init
This commit is contained in:
161
java/org/apache/tomcat/buildutil/CheckEol.java
Normal file
161
java/org/apache/tomcat/buildutil/CheckEol.java
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.tomcat.buildutil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
/**
|
||||
* Ant task that checks that all the files in the given fileset have end-of-line
|
||||
* delimiters that are appropriate for the current OS.
|
||||
*
|
||||
* <p>
|
||||
* The goal is to check whether we have problems with svn:eol-style property
|
||||
* when files are committed on one OS and then checked on another one.
|
||||
*/
|
||||
public class CheckEol extends Task {
|
||||
|
||||
/** The files to be checked */
|
||||
private final List<FileSet> filesets = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Sets the files to be checked
|
||||
*
|
||||
* @param fs The fileset to be checked.
|
||||
*/
|
||||
public void addFileset( FileSet fs ) {
|
||||
filesets.add( fs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the check
|
||||
*
|
||||
* @throws BuildException if an error occurs during execution of
|
||||
* this task.
|
||||
*/
|
||||
@Override
|
||||
public void execute() throws BuildException {
|
||||
|
||||
Mode mode = null;
|
||||
if ("\n".equals(System.lineSeparator())) {
|
||||
mode = Mode.LF;
|
||||
} else if ("\r\n".equals(System.lineSeparator())) {
|
||||
mode = Mode.CRLF;
|
||||
} else {
|
||||
log("Line ends check skipped, because OS line ends setting is neither LF nor CRLF.",
|
||||
Project.MSG_VERBOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
List<CheckFailure> errors = new ArrayList<>();
|
||||
|
||||
// Step through each file and check.
|
||||
for (FileSet fs : filesets) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File basedir = ds.getBasedir();
|
||||
String[] files = ds.getIncludedFiles();
|
||||
if (files.length > 0) {
|
||||
log("Checking line ends in " + files.length + " file(s)");
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File file = new File(basedir, files[i]);
|
||||
log("Checking file '" + file + "' for correct line ends",
|
||||
Project.MSG_DEBUG);
|
||||
try {
|
||||
check(file, errors, mode);
|
||||
} catch (IOException e) {
|
||||
throw new BuildException("Could not check file '"
|
||||
+ file.getAbsolutePath() + "'", e);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
log("Done line ends check in " + count + " file(s), "
|
||||
+ errors.size() + " error(s) found.");
|
||||
}
|
||||
if (errors.size() > 0) {
|
||||
String message = "The following files have wrong line ends: "
|
||||
+ errors;
|
||||
// We need to explicitly write the message to the log, because
|
||||
// long BuildException messages may be trimmed. E.g. I observed
|
||||
// this problem with Eclipse IDE 3.7.
|
||||
log(message, Project.MSG_ERR);
|
||||
throw new BuildException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Mode {
|
||||
LF, CRLF
|
||||
}
|
||||
|
||||
private static class CheckFailure {
|
||||
private final File file;
|
||||
private final int line;
|
||||
private final String value;
|
||||
|
||||
public CheckFailure(File file, int line, String value) {
|
||||
this.file = file;
|
||||
this.line = line;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return System.lineSeparator() + file + ": uses " + value + " on line " + line;
|
||||
}
|
||||
}
|
||||
|
||||
private void check(File file, List<CheckFailure> errors, Mode mode) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(file);
|
||||
BufferedInputStream is = new BufferedInputStream(fis)) {
|
||||
int line = 1;
|
||||
int prev = -1;
|
||||
int ch;
|
||||
while ((ch = is.read()) != -1) {
|
||||
if (ch == '\n') {
|
||||
if (mode == Mode.LF && prev == '\r') {
|
||||
errors.add(new CheckFailure(file, line, "CRLF"));
|
||||
return;
|
||||
} else if (mode == Mode.CRLF && prev != '\r') {
|
||||
errors.add(new CheckFailure(file, line, "LF"));
|
||||
return;
|
||||
}
|
||||
line++;
|
||||
} else if (prev == '\r') {
|
||||
errors.add(new CheckFailure(file, line, "CR"));
|
||||
return;
|
||||
}
|
||||
prev = ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
413
java/org/apache/tomcat/buildutil/SignCode.java
Normal file
413
java/org/apache/tomcat/buildutil/SignCode.java
Normal file
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javax.xml.soap.MessageFactory;
|
||||
import javax.xml.soap.SOAPBody;
|
||||
import javax.xml.soap.SOAPConnection;
|
||||
import javax.xml.soap.SOAPConnectionFactory;
|
||||
import javax.xml.soap.SOAPConstants;
|
||||
import javax.xml.soap.SOAPElement;
|
||||
import javax.xml.soap.SOAPEnvelope;
|
||||
import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
import javax.xml.soap.SOAPPart;
|
||||
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* Ant task that submits a file to the Digicert (formally Symantec) code-signing
|
||||
* service. The service is defined by the published
|
||||
* <a href="https://api.ws.digicert.com/webtrust/SigningService?wsdl">WSDL</a>.
|
||||
* Note that while the service has migrated to a Digicert domain, the namespace
|
||||
* continues to use a Symantec domain.
|
||||
*/
|
||||
public class SignCode extends Task {
|
||||
|
||||
private static final URL SIGNING_SERVICE_URL;
|
||||
|
||||
private static final String NS = "cod";
|
||||
|
||||
private static final MessageFactory SOAP_MSG_FACTORY;
|
||||
|
||||
static {
|
||||
try {
|
||||
SIGNING_SERVICE_URL = new URL(
|
||||
"https://api-appsec.pki.digicert.com/webtrust/SigningService");
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
try {
|
||||
SOAP_MSG_FACTORY = MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
|
||||
} catch (SOAPException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<FileSet> filesets = new ArrayList<>();
|
||||
private String userName;
|
||||
private String password;
|
||||
private String partnerCode;
|
||||
private String keyStore;
|
||||
private String keyStorePassword;
|
||||
private String applicationName;
|
||||
private String applicationVersion;
|
||||
private String signingService;
|
||||
private boolean debug;
|
||||
|
||||
public void addFileset(FileSet fileset) {
|
||||
filesets.add(fileset);
|
||||
}
|
||||
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
|
||||
public void setPartnerCode(String partnerCode) {
|
||||
this.partnerCode = partnerCode;
|
||||
}
|
||||
|
||||
|
||||
public void setKeyStore(String keyStore) {
|
||||
this.keyStore = keyStore;
|
||||
}
|
||||
|
||||
|
||||
public void setKeyStorePassword(String keyStorePassword) {
|
||||
this.keyStorePassword = keyStorePassword;
|
||||
}
|
||||
|
||||
|
||||
public void setApplicationName(String applicationName) {
|
||||
this.applicationName = applicationName;
|
||||
}
|
||||
|
||||
|
||||
public void setApplicationVersion(String applicationVersion) {
|
||||
this.applicationVersion = applicationVersion;
|
||||
}
|
||||
|
||||
|
||||
public void setSigningService(String signingService) {
|
||||
this.signingService = signingService;
|
||||
}
|
||||
|
||||
|
||||
public void setDebug(String debug) {
|
||||
this.debug = Boolean.parseBoolean(debug);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute() throws BuildException {
|
||||
|
||||
List<File> filesToSign = new ArrayList<>();
|
||||
|
||||
// Process the filesets and populate the list of files that need to be
|
||||
// signed.
|
||||
for (FileSet fileset : filesets) {
|
||||
DirectoryScanner ds = fileset.getDirectoryScanner(getProject());
|
||||
File basedir = ds.getBasedir();
|
||||
String[] files = ds.getIncludedFiles();
|
||||
if (files.length > 0) {
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
File file = new File(basedir, files[i]);
|
||||
filesToSign.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the TLS client
|
||||
System.setProperty("javax.net.ssl.keyStore", keyStore);
|
||||
System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
|
||||
|
||||
try {
|
||||
String signingSetID = makeSigningRequest(filesToSign);
|
||||
downloadSignedFiles(filesToSign, signingSetID);
|
||||
} catch (SOAPException | IOException e) {
|
||||
throw new BuildException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String makeSigningRequest(List<File> filesToSign) throws SOAPException, IOException {
|
||||
log("Constructing the code signing request");
|
||||
|
||||
SOAPMessage message = SOAP_MSG_FACTORY.createMessage();
|
||||
SOAPBody body = populateEnvelope(message, NS);
|
||||
|
||||
SOAPElement requestSigning = body.addChildElement("requestSigning", NS);
|
||||
SOAPElement requestSigningRequest =
|
||||
requestSigning.addChildElement("requestSigningRequest", NS);
|
||||
|
||||
addCredentials(requestSigningRequest, this.userName, this.password, this.partnerCode);
|
||||
|
||||
SOAPElement applicationName =
|
||||
requestSigningRequest.addChildElement("applicationName", NS);
|
||||
applicationName.addTextNode(this.applicationName);
|
||||
|
||||
SOAPElement applicationVersion =
|
||||
requestSigningRequest.addChildElement("applicationVersion", NS);
|
||||
applicationVersion.addTextNode(this.applicationVersion);
|
||||
|
||||
SOAPElement signingServiceName =
|
||||
requestSigningRequest.addChildElement("signingServiceName", NS);
|
||||
signingServiceName.addTextNode(this.signingService);
|
||||
|
||||
List<String> fileNames = getFileNames(filesToSign);
|
||||
|
||||
SOAPElement commaDelimitedFileNames =
|
||||
requestSigningRequest.addChildElement("commaDelimitedFileNames", NS);
|
||||
commaDelimitedFileNames.addTextNode(StringUtils.join(fileNames));
|
||||
|
||||
SOAPElement application =
|
||||
requestSigningRequest.addChildElement("application", NS);
|
||||
application.addTextNode(getApplicationString(fileNames, filesToSign));
|
||||
|
||||
// Send the message
|
||||
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
|
||||
SOAPConnection connection = soapConnectionFactory.createConnection();
|
||||
|
||||
log("Sending signing request to server and waiting for response");
|
||||
SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
|
||||
|
||||
if (debug) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(2 * 1024);
|
||||
response.writeTo(baos);
|
||||
log(baos.toString("UTF-8"));
|
||||
}
|
||||
|
||||
log("Processing response");
|
||||
SOAPElement responseBody = response.getSOAPBody();
|
||||
|
||||
// Should come back signed
|
||||
NodeList bodyNodes = responseBody.getChildNodes();
|
||||
NodeList requestSigningResponseNodes = bodyNodes.item(0).getChildNodes();
|
||||
NodeList returnNodes = requestSigningResponseNodes.item(0).getChildNodes();
|
||||
|
||||
String signingSetID = null;
|
||||
String signingSetStatus = null;
|
||||
|
||||
for (int i = 0; i < returnNodes.getLength(); i++) {
|
||||
Node returnNode = returnNodes.item(i);
|
||||
if (returnNode.getLocalName().equals("signingSetID")) {
|
||||
signingSetID = returnNode.getTextContent();
|
||||
} else if (returnNode.getLocalName().equals("signingSetStatus")) {
|
||||
signingSetStatus = returnNode.getTextContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!signingService.contains("TEST") && !"SIGNED".equals(signingSetStatus) ||
|
||||
signingService.contains("TEST") && !"INITIALIZED".equals(signingSetStatus) ) {
|
||||
throw new BuildException("Signing failed. Status was: " + signingSetStatus);
|
||||
}
|
||||
|
||||
return signingSetID;
|
||||
}
|
||||
|
||||
|
||||
private void downloadSignedFiles(List<File> filesToSign, String id)
|
||||
throws SOAPException, IOException {
|
||||
|
||||
log("Downloading signed files. The signing set ID is: " + id);
|
||||
|
||||
SOAPMessage message = SOAP_MSG_FACTORY.createMessage();
|
||||
SOAPBody body = populateEnvelope(message, NS);
|
||||
|
||||
SOAPElement getSigningSetDetails = body.addChildElement("getSigningSetDetails", NS);
|
||||
SOAPElement getSigningSetDetailsRequest =
|
||||
getSigningSetDetails.addChildElement("getSigningSetDetailsRequest", NS);
|
||||
|
||||
addCredentials(getSigningSetDetailsRequest, this.userName, this.password, this.partnerCode);
|
||||
|
||||
SOAPElement signingSetID =
|
||||
getSigningSetDetailsRequest.addChildElement("signingSetID", NS);
|
||||
signingSetID.addTextNode(id);
|
||||
|
||||
SOAPElement returnApplication =
|
||||
getSigningSetDetailsRequest.addChildElement("returnApplication", NS);
|
||||
returnApplication.addTextNode("true");
|
||||
|
||||
// Send the message
|
||||
SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
|
||||
SOAPConnection connection = soapConnectionFactory.createConnection();
|
||||
|
||||
log("Requesting signed files from server and waiting for response");
|
||||
SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
|
||||
|
||||
log("Processing response");
|
||||
SOAPElement responseBody = response.getSOAPBody();
|
||||
|
||||
// Check for success
|
||||
|
||||
// Extract the signed file(s) from the ZIP
|
||||
NodeList bodyNodes = responseBody.getChildNodes();
|
||||
NodeList getSigningSetDetailsResponseNodes = bodyNodes.item(0).getChildNodes();
|
||||
NodeList returnNodes = getSigningSetDetailsResponseNodes.item(0).getChildNodes();
|
||||
|
||||
String result = null;
|
||||
String data = null;
|
||||
|
||||
for (int i = 0; i < returnNodes.getLength(); i++) {
|
||||
Node returnNode = returnNodes.item(i);
|
||||
if (returnNode.getLocalName().equals("result")) {
|
||||
result = returnNode.getChildNodes().item(0).getTextContent();
|
||||
} else if (returnNode.getLocalName().equals("signingSet")) {
|
||||
data = returnNode.getChildNodes().item(1).getTextContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!"0".equals(result)) {
|
||||
throw new BuildException("Download failed. Result code was: " + result);
|
||||
}
|
||||
|
||||
extractFilesFromApplicationString(data, filesToSign);
|
||||
}
|
||||
|
||||
|
||||
private static SOAPBody populateEnvelope(SOAPMessage message, String namespace)
|
||||
throws SOAPException {
|
||||
SOAPPart soapPart = message.getSOAPPart();
|
||||
SOAPEnvelope envelope = soapPart.getEnvelope();
|
||||
envelope.addNamespaceDeclaration(
|
||||
"soapenv","http://schemas.xmlsoap.org/soap/envelope/");
|
||||
envelope.addNamespaceDeclaration(
|
||||
namespace,"http://api.ws.symantec.com/webtrust/codesigningservice");
|
||||
return envelope.getBody();
|
||||
}
|
||||
|
||||
|
||||
private static void addCredentials(SOAPElement requestSigningRequest,
|
||||
String user, String pwd, String code) throws SOAPException {
|
||||
SOAPElement authToken = requestSigningRequest.addChildElement("authToken", NS);
|
||||
SOAPElement userName = authToken.addChildElement("userName", NS);
|
||||
userName.addTextNode(user);
|
||||
SOAPElement password = authToken.addChildElement("password", NS);
|
||||
password.addTextNode(pwd);
|
||||
SOAPElement partnerCode = authToken.addChildElement("partnerCode", NS);
|
||||
partnerCode.addTextNode(code);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signing service requires unique files names. Since files will be returned
|
||||
* in order, use dummy names that we know are unique but retain the file
|
||||
* extension since the signing service appears to use it to figure out what
|
||||
* to sign and how to sign it.
|
||||
*/
|
||||
private static List<String> getFileNames(List<File> filesToSign) {
|
||||
List<String> result = new ArrayList<>(filesToSign.size());
|
||||
|
||||
for (int i = 0; i < filesToSign.size(); i++) {
|
||||
File f = filesToSign.get(i);
|
||||
String fileName = f.getName();
|
||||
int extIndex = fileName.lastIndexOf('.');
|
||||
String newName;
|
||||
if (extIndex < 0) {
|
||||
newName = Integer.toString(i);
|
||||
} else {
|
||||
newName = Integer.toString(i) + fileName.substring(extIndex);
|
||||
}
|
||||
result.add(newName);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Zips the files, base 64 encodes the resulting zip and then returns the
|
||||
* string. It would be far more efficient to stream this directly to the
|
||||
* signing server but the files that need to be signed are relatively small
|
||||
* and this simpler to write.
|
||||
*
|
||||
* @param fileNames Modified names of files
|
||||
* @param files Files to be signed
|
||||
*/
|
||||
private static String getApplicationString(List<String> fileNames, List<File> files)
|
||||
throws IOException {
|
||||
// 16 MB should be more than enough for Tomcat
|
||||
// TODO: Refactoring this entire class so it uses streaming rather than
|
||||
// buffering the entire set of files in memory would make it more
|
||||
// widely useful.
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(16 * 1024 * 1024);
|
||||
try (ZipOutputStream zos = new ZipOutputStream(baos)) {
|
||||
byte[] buf = new byte[32 * 1024];
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
try (FileInputStream fis = new FileInputStream(files.get(i))) {
|
||||
ZipEntry zipEntry = new ZipEntry(fileNames.get(i));
|
||||
zos.putNextEntry(zipEntry);
|
||||
int numRead;
|
||||
while ( (numRead = fis.read(buf)) >= 0) {
|
||||
zos.write(buf, 0, numRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Base64.encodeBase64String(baos.toByteArray());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes base64 encoding, unzips the files and writes the new files over
|
||||
* the top of the old ones.
|
||||
*/
|
||||
private static void extractFilesFromApplicationString(String data, List<File> files)
|
||||
throws IOException {
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decodeBase64(data));
|
||||
try (ZipInputStream zis = new ZipInputStream(bais)) {
|
||||
byte[] buf = new byte[32 * 1024];
|
||||
for (int i = 0; i < files.size(); i ++) {
|
||||
try (FileOutputStream fos = new FileOutputStream(files.get(i))) {
|
||||
zis.getNextEntry();
|
||||
int numRead;
|
||||
while ( (numRead = zis.read(buf)) >= 0) {
|
||||
fos.write(buf, 0 , numRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
179
java/org/apache/tomcat/buildutil/Txt2Html.java
Normal file
179
java/org/apache/tomcat/buildutil/Txt2Html.java
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.tomcat.buildutil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Project;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
/**
|
||||
* Ant task to convert a given set of files from Text to HTML.
|
||||
* Inserts an HTML header including pre tags and replaces special characters
|
||||
* with their HTML escaped equivalents.
|
||||
*
|
||||
* <p>This task is currently used by the ant script to build our examples</p>
|
||||
*
|
||||
* @author Mark Roth
|
||||
*/
|
||||
public class Txt2Html
|
||||
extends Task
|
||||
{
|
||||
|
||||
/** The directory to contain the resulting files */
|
||||
private File todir;
|
||||
|
||||
/** The file to be converted into HTML */
|
||||
private final List<FileSet> filesets = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* The encoding of the source files (.java and .jsp). Once they use
|
||||
* UTF-8, this will need to be updated.
|
||||
*/
|
||||
private static final String SOURCE_ENCODING = "ISO-8859-1";
|
||||
|
||||
/**
|
||||
* Line terminator to be used for separating lines of the generated
|
||||
* HTML page, to be independent from "line.separator" system property.
|
||||
*/
|
||||
private static final String LINE_SEPARATOR = "\r\n";
|
||||
|
||||
/**
|
||||
* Sets the directory to contain the resulting files
|
||||
*
|
||||
* @param todir The directory
|
||||
*/
|
||||
public void setTodir( File todir ) {
|
||||
this.todir = todir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the files to be converted into HTML
|
||||
*
|
||||
* @param fs The fileset to be converted.
|
||||
*/
|
||||
public void addFileset( FileSet fs ) {
|
||||
filesets.add( fs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the conversion
|
||||
*
|
||||
* @throws BuildException if an error occurs during execution of
|
||||
* this task.
|
||||
*/
|
||||
@Override
|
||||
public void execute()
|
||||
throws BuildException
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
// Step through each file and convert.
|
||||
for (FileSet fs : filesets) {
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File basedir = ds.getBasedir();
|
||||
String[] files = ds.getIncludedFiles();
|
||||
for( int i = 0; i < files.length; i++ ) {
|
||||
File from = new File( basedir, files[i] );
|
||||
File to = new File( todir, files[i] + ".html" );
|
||||
if( !to.exists() ||
|
||||
(from.lastModified() > to.lastModified()) )
|
||||
{
|
||||
log( "Converting file '" + from.getAbsolutePath() +
|
||||
"' to '" + to.getAbsolutePath(), Project.MSG_VERBOSE );
|
||||
try {
|
||||
convert( from, to );
|
||||
}
|
||||
catch( IOException e ) {
|
||||
throw new BuildException( "Could not convert '" +
|
||||
from.getAbsolutePath() + "' to '" +
|
||||
to.getAbsolutePath() + "'", e );
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if( count > 0 ) {
|
||||
log( "Converted " + count + " file" + (count > 1 ? "s" : "") +
|
||||
" to " + todir.getAbsolutePath() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual copy and conversion
|
||||
*
|
||||
* @param from The input file
|
||||
* @param to The output file
|
||||
* @throws IOException Thrown if an error occurs during the conversion
|
||||
*/
|
||||
private void convert( File from, File to )
|
||||
throws IOException
|
||||
{
|
||||
// Open files:
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream(from), SOURCE_ENCODING))) {
|
||||
try (PrintWriter out = new PrintWriter(new OutputStreamWriter(
|
||||
new FileOutputStream(to), "UTF-8"))) {
|
||||
|
||||
// Output header:
|
||||
out.print("<!DOCTYPE html><html><head><meta charset=\"UTF-8\" />"
|
||||
+ "<title>Source Code</title></head><body><pre>" );
|
||||
|
||||
// Convert, line-by-line:
|
||||
String line;
|
||||
while( (line = in.readLine()) != null ) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int len = line.length();
|
||||
for( int i = 0; i < len; i++ ) {
|
||||
char c = line.charAt( i );
|
||||
switch( c ) {
|
||||
case '&':
|
||||
result.append( "&" );
|
||||
break;
|
||||
case '<':
|
||||
result.append( "<" );
|
||||
break;
|
||||
default:
|
||||
result.append( c );
|
||||
}
|
||||
}
|
||||
out.print( result.toString() + LINE_SEPARATOR );
|
||||
}
|
||||
|
||||
// Output footer:
|
||||
out.print( "</pre></body></html>" );
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
65
java/org/apache/tomcat/buildutil/translate/BackportBase.java
Normal file
65
java/org/apache/tomcat/buildutil/translate/BackportBase.java
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil.translate;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Base class providing common implementation for back-port utilities.
|
||||
*/
|
||||
public abstract class BackportBase {
|
||||
|
||||
protected final Map<String,Properties> sourceTranslations = new HashMap<>();
|
||||
protected final Map<String,Properties> targetTranslations = new HashMap<>();
|
||||
protected final File targetRoot;
|
||||
protected final Properties sourceEnglish;
|
||||
protected final Properties targetEnglish;
|
||||
protected final File storageDir;
|
||||
|
||||
protected BackportBase(String... args) throws IOException {
|
||||
if (args.length != 1) {
|
||||
throw new IllegalArgumentException("Missing back-port target");
|
||||
}
|
||||
targetRoot = new File(args[0]);
|
||||
|
||||
if (!targetRoot.isDirectory()) {
|
||||
throw new IllegalArgumentException("Back-port target not a directory");
|
||||
}
|
||||
|
||||
File sourceRoot = new File(".");
|
||||
for (String dir : Constants.SEARCH_DIRS) {
|
||||
File directory = new File(dir);
|
||||
Utils.processDirectory(sourceRoot, directory, sourceTranslations);
|
||||
}
|
||||
|
||||
for (String dir : Constants.SEARCH_DIRS) {
|
||||
File directory = new File(targetRoot, dir);
|
||||
Utils.processDirectory(targetRoot, directory, targetTranslations);
|
||||
}
|
||||
|
||||
sourceEnglish = sourceTranslations.get("");
|
||||
targetEnglish = targetTranslations.get("");
|
||||
|
||||
storageDir = new File(targetRoot, Constants.STORAGE_DIR);
|
||||
}
|
||||
|
||||
protected abstract void execute() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil.translate;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Generates a set of English property files to back-port updates to a previous
|
||||
* version. Where a key exists in the source and target versions the value is
|
||||
* copied from the source to the target, overwriting the value in the target.
|
||||
* The expectation is that the changes will be manually reviewed before
|
||||
* committing them.
|
||||
*/
|
||||
public class BackportEnglish extends BackportBase {
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
BackportEnglish backport = new BackportEnglish(args);
|
||||
backport.execute();
|
||||
}
|
||||
|
||||
|
||||
protected BackportEnglish(String[] args) throws IOException {
|
||||
super(args);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void execute() throws IOException {
|
||||
for (Object key : sourceEnglish.keySet()) {
|
||||
if (targetEnglish.containsKey(key)) {
|
||||
targetEnglish.put(key, sourceEnglish.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
Utils.export("", targetEnglish, storageDir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil.translate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Generates a set of translated property files to back-port updates to a
|
||||
* previous version. If the source and target use the same value for the English
|
||||
* key then any translated value for that key is copied from the source to the
|
||||
* target.
|
||||
*/
|
||||
public class BackportTranslations extends BackportBase {
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
BackportTranslations backport = new BackportTranslations(args);
|
||||
backport.execute();
|
||||
}
|
||||
|
||||
protected BackportTranslations(String[] args) throws IOException {
|
||||
super(args);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void execute() throws IOException {
|
||||
for (String langauge : targetTranslations.keySet()) {
|
||||
// Skip source
|
||||
if (langauge.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Properties sourceTranslated = sourceTranslations.get(langauge);
|
||||
Properties targetTranslated = targetTranslations.get(langauge);
|
||||
if (targetTranslated == null) {
|
||||
targetTranslated = new Properties();
|
||||
targetTranslations.put(langauge, targetTranslated);
|
||||
}
|
||||
|
||||
for (Object key : targetEnglish.keySet()) {
|
||||
if (sourceTranslated.containsKey(key) &&
|
||||
targetEnglish.get(key).equals(sourceEnglish.get(key))) {
|
||||
|
||||
targetTranslated.put(key, sourceTranslated.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove translated values for keys that have been removed
|
||||
Iterator<Map.Entry<Object,Object>> iter = targetTranslated.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<Object,Object> entry = iter.next();
|
||||
if (!targetEnglish.containsKey(entry.getKey())) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
Utils.export(langauge, targetTranslated, storageDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
java/org/apache/tomcat/buildutil/translate/Constants.java
Normal file
29
java/org/apache/tomcat/buildutil/translate/Constants.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil.translate;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static final String L10N_PREFIX = "LocalStrings";
|
||||
public static final String L10N_SUFFIX = ".properties";
|
||||
|
||||
public static final String[] SEARCH_DIRS = new String[] { "java", "webapps" };
|
||||
|
||||
public static final String STORAGE_DIR = ".settings/translations";
|
||||
|
||||
public static final String END_PACKAGE_MARKER = ".zzz.";
|
||||
}
|
||||
133
java/org/apache/tomcat/buildutil/translate/Import.java
Normal file
133
java/org/apache/tomcat/buildutil/translate/Import.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil.translate;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
public class Import {
|
||||
|
||||
public static void main(String... args) throws IOException {
|
||||
File root = new File(Constants.STORAGE_DIR);
|
||||
|
||||
for (File f : root.listFiles()) {
|
||||
// Not robust but good enough
|
||||
if (f.isFile() && f.getName().startsWith(Constants.L10N_PREFIX)) {
|
||||
processFile(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private static void processFile(File f) throws IOException {
|
||||
String language = Utils.getLanguage(f.getName());
|
||||
|
||||
// Unlike the master branch, don't skip the original so we can import
|
||||
// updates to the English translations
|
||||
Properties props = Utils.load(f);
|
||||
Object[] objKeys = props.keySet().toArray();
|
||||
Arrays.sort(objKeys);
|
||||
|
||||
String currentPkg = null;
|
||||
Writer w = null;
|
||||
String currentGroup = "zzz";
|
||||
|
||||
for (Object objKey : objKeys) {
|
||||
String key = (String) objKey;
|
||||
CompositeKey cKey = new CompositeKey(key);
|
||||
|
||||
if (!cKey.pkg.equals(currentPkg)) {
|
||||
currentPkg = cKey.pkg;
|
||||
if (w != null) {
|
||||
w.close();
|
||||
}
|
||||
File outFile = new File(currentPkg.replace('.', File.separatorChar), Constants.L10N_PREFIX + language + Constants.L10N_SUFFIX);
|
||||
FileOutputStream fos = new FileOutputStream(outFile);
|
||||
w = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
|
||||
insertLicense(w);
|
||||
}
|
||||
|
||||
if (!currentGroup.equals(cKey.group)) {
|
||||
currentGroup = cKey.group;
|
||||
w.write(System.lineSeparator());
|
||||
}
|
||||
|
||||
w.write(cKey.key + "=" + Utils.formatValue(props.getProperty(key)));
|
||||
w.write(System.lineSeparator());
|
||||
}
|
||||
if (w != null) {
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void insertLicense(Writer w) throws IOException {
|
||||
w.write("# Licensed to the Apache Software Foundation (ASF) under one or more");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# contributor license agreements. See the NOTICE file distributed with");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# this work for additional information regarding copyright ownership.");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# The ASF licenses this file to You under the Apache License, Version 2.0");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# (the \"License\"); you may not use this file except in compliance with");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# the License. You may obtain a copy of the License at");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("#");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# http://www.apache.org/licenses/LICENSE-2.0");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("#");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# Unless required by applicable law or agreed to in writing, software");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# distributed under the License is distributed on an \"AS IS\" BASIS,");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# See the License for the specific language governing permissions and");
|
||||
w.write(System.lineSeparator());
|
||||
w.write("# limitations under the License.");
|
||||
w.write(System.lineSeparator());
|
||||
}
|
||||
private static class CompositeKey {
|
||||
|
||||
public final String pkg;
|
||||
public final String key;
|
||||
public final String group;
|
||||
|
||||
public CompositeKey(String in) {
|
||||
int posPkg = in.indexOf(Constants.END_PACKAGE_MARKER);
|
||||
pkg = in.substring(0, posPkg);
|
||||
key = in.substring(posPkg + Constants.END_PACKAGE_MARKER.length());
|
||||
int posGroup = key.indexOf('.');
|
||||
if (posGroup == -1) {
|
||||
group = "";
|
||||
} else {
|
||||
group = key.substring(0, posGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
149
java/org/apache/tomcat/buildutil/translate/Utils.java
Normal file
149
java/org/apache/tomcat/buildutil/translate/Utils.java
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.tomcat.buildutil.translate;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Utils {
|
||||
|
||||
private static final Pattern ADD_CONTINUATION = Pattern.compile("\\n", Pattern.MULTILINE);
|
||||
private static final Pattern ESCAPE_LEADING_SPACE = Pattern.compile("^(\\s)", Pattern.MULTILINE);
|
||||
private static final Pattern FIX_SINGLE_QUOTE = Pattern.compile("(?<!')'(?!')", Pattern.MULTILINE);
|
||||
|
||||
private Utils() {
|
||||
// Utility class. Hide default constructor.
|
||||
}
|
||||
|
||||
|
||||
static String getLanguage(String name) {
|
||||
return name.substring(Constants.L10N_PREFIX.length(), name.length() - Constants.L10N_SUFFIX.length());
|
||||
}
|
||||
|
||||
|
||||
static Properties load(File f) {
|
||||
Properties props = new Properties();
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(f);
|
||||
Reader r = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
|
||||
props.load(r);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
|
||||
static String formatValue(String in) {
|
||||
String result = ADD_CONTINUATION.matcher(in).replaceAll("\\\\n\\\\\n");
|
||||
if (result.endsWith("\\\n")) {
|
||||
result = result.substring(0, result.length() - 2);
|
||||
}
|
||||
result = ESCAPE_LEADING_SPACE.matcher(result).replaceAll("\\\\$1");
|
||||
|
||||
if (result.contains("\n\\\t")) {
|
||||
result = result.replace("\n\\\t", "\n\\t");
|
||||
}
|
||||
|
||||
if (result.contains("[{0}]")) {
|
||||
result = FIX_SINGLE_QUOTE.matcher(result).replaceAll("''");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void processDirectory(File root, File dir, Map<String,Properties> translations) throws IOException {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
throw new IllegalArgumentException("Not a directory [" + dir.getAbsolutePath() + "]");
|
||||
}
|
||||
for (File f : files) {
|
||||
if (f.isDirectory()) {
|
||||
processDirectory(root, f, translations);
|
||||
} else if (f.isFile()) {
|
||||
processFile(root, f, translations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void processFile(File root, File f, Map<String,Properties> translations) throws IOException {
|
||||
String name = f.getName();
|
||||
|
||||
// non-l10n files
|
||||
if (!name.startsWith(Constants.L10N_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine language
|
||||
String language = Utils.getLanguage(name);
|
||||
|
||||
String keyPrefix = getKeyPrefix(root, f);
|
||||
Properties props = Utils.load(f);
|
||||
|
||||
// Create a Map for the language if one does not exist.
|
||||
Properties translation = translations.get(language);
|
||||
if (translation == null) {
|
||||
translation = new Properties();
|
||||
translations.put(language, translation);
|
||||
}
|
||||
|
||||
// Add the properties from this file to the combined file, prefixing the
|
||||
// key with the package name to ensure uniqueness.
|
||||
for (Object obj : props.keySet()) {
|
||||
String key = (String) obj;
|
||||
String value = props.getProperty(key);
|
||||
|
||||
translation.put(keyPrefix + key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static String getKeyPrefix(File root, File f) throws IOException {
|
||||
String prefix = f.getParentFile().getCanonicalPath();
|
||||
prefix = prefix.substring(root.getCanonicalPath().length() + 1);
|
||||
prefix = prefix.replace(File.separatorChar, '.');
|
||||
prefix = prefix + Constants.END_PACKAGE_MARKER;
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
static void export(String language, Properties translation, File storageDir) {
|
||||
File out = new File(storageDir, Constants.L10N_PREFIX + language + Constants.L10N_SUFFIX);
|
||||
try (FileOutputStream fos = new FileOutputStream(out);
|
||||
Writer w = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
|
||||
String[] keys = translation.keySet().toArray(new String[0]);
|
||||
Arrays.sort(keys);
|
||||
for (Object key : keys) {
|
||||
w.write(key + "=" + Utils.formatValue(translation.getProperty((String) key)) + "\n");
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user