/*
* � Copyright IBM Corp. 2011, 2014
*
* Licensed 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.
*/
/*
* Created on 13-Apr-2005
* Created by Maire Kehoe (mkehoe@ie.ibm.com)
*/
package com.ibm.xsp.tools;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.ibm.xsp.page.translator.Lines;
import com.ibm.xsp.registry.parse.ElementUtil;
import com.ibm.xsp.registry.parse.ElementUtil.ElementIterator;
import com.ibm.xsp.registry.parse.FileConstants;
import com.ibm.xsp.registry.parse.ParseUtil;
import com.ibm.xsp.tools.flatten.ConfigFlattenerInput;
import com.ibm.xsp.tools.flatten.PropertiesExtractor;
/**
*
* @author Maire Kehoe (mkehoe@ie.ibm.com)
*/
public class ConfigFlattener {
private static String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
public static final boolean OUTPUT_PROPERTIES_FILES = true;
public static final boolean PUT_KEYS_IN_DOCUMENT = true;
public static void main(String[] args) {
new ConfigFlattener().run( new ConfigFlattenerInput(args));
}
public void run(ConfigFlattenerInput input){
if( ! input.isValid()){
input.printUsage();
return;
}
try {
flatten(input);
} catch (RuntimeException ex){
ex.printStackTrace();
throw ex;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (Error e) {
e.printStackTrace();
throw e;
}
}
private void flatten(ConfigFlattenerInput input)
throws Exception {
input.print();
Document document = ToolsUtil.readInFile(input.getInFile());
if( input.isFacesConfigMode() ){
Element root = document.getDocumentElement();
removeElements(root, "component");
removeElements(root, "attribute");
removeElements(root, "renderer-extension");
removeElements(root, "description");
removeElements(root, "display-name");
}
else if (! input.isInlineMode() && OUTPUT_PROPERTIES_FILES) {
// remove attributes & properties ignored by the registry
removeNonTagAttributes(document.getDocumentElement());
// ignore all warnings in core-test
boolean printWarnings = !(input.isIgnoreWarnings());
// this does 2 things:
// -) builds a string containing lines for the .properties file
// -) inserts %key% in the document instead of the messages
String extactedProperties = new PropertiesExtractor()
.changeDocument(document, PUT_KEYS_IN_DOCUMENT,
printWarnings);
File extraPropsFile = getExtraPropsFile(input.getInFileName(), input.getExtraPropsFolder());
// check the document's category %key%s are in the -extra file
validateComplexCategories(document, extraPropsFile);
String props = "";
props = appendCopyrightProps(props);
if( input.isDoNotTranslate() ){
props += "## G11N " + "DNT -- Do Not Translate " + NEWLINE;
}else{
props += "# NLS_ENCODING=UNICODE "+NEWLINE;
props += "# NLS_MARKUP=IBMNJDK2 "+NEWLINE;
// \\wsa4.notesdev.ibm.com\workplace\dailybuilds\DSI8.5\
// DSI8.5_20080624.0630\lwp04.wct-des\lwp\build\trans\results\CHKPII_SAUI.txt
// used to contain:
// core-actions_en.properties JAVA-PRB 922
// Unknown single quote handling for this file.
// Special NLS_MESSAGEFORMAT comment must be added.
// before this line was added:
props += "# NLS_MESSAGEFORMAT_NONE"+NEWLINE;
// note, must concat strings with + as, so ConfigFlattener.java
// does not show up as to be translated:
props += "## G11N" + " SA "
+ "UI -- Special IT Audience resources follow"
+ NEWLINE;
}
props = appendExtraProps(props, extraPropsFile);
props += extactedProperties;
ToolsUtil.writeOut(input.getOutPropertiesFile(), props);
}
if( ! input.isInlineMode() ){
removeComments(document);
}
removeElements(document.getDocumentElement(), "icon");
removeXmlBaseAttributes(document.getDocumentElement(), input.isInlineMode());
String str = ToolsUtil.convertToString(document);
if( input.isInlineMode() ){
str = insertLine(str, "<!-- The raw files are not used at runtime nor in Designer, and will be removed from the build output. -->"+NEWLINE);
}
str = changeNewlines(str);
str = prependCopyrightXml(str);
ToolsUtil.writeOut(input.getOutFile(), str);
System.out.println("flattened.");
}
/**
* @param str
*/
private String changeNewlines(String str) {
// Since the 9.0.1_N / 2014-11-09 change to the xml serializer
// the xml will be using the OS newline, but this ConfigFlattener
// and related classes may be using \n, and the copyright text files
// may have any possible newline.
// Convert from random newlines to the operating system newline
String modOne = str.replace("\r\n", "\n");
String modTwo = modOne.replace("\r","\n");
String modThree = modTwo.replace("\n",NEWLINE);
return modThree;
}
/**
* @param document
* @return if this node was removed.
*/
private boolean removeNonTagAttributes(Element element) {
// recursively remove from children
NodeList kids = element.getChildNodes();
for (int i = 0; i < kids.getLength(); i++) {
Node kid = kids.item(i);
if( Node.ELEMENT_NODE != kid.getNodeType() ){
continue;
}
if( removeNonTagAttributes((Element) kid) ){
i--;
}
}
String elemName = element.getNodeName();
boolean isProp = FileConstants.PROPERTY.equals(elemName);
if( !isProp && !FileConstants.ATTRIBUTE.equals(elemName)){
return false;
}
String extName = isProp? FileConstants.PROPERTY_EXTENSION:FileConstants.ATTRIBUTE_EXTENSION;
// <tag-attribute>false</tag-attribute>
String tagAttrStr = ElementUtil.extractValue(
ElementUtil.getExtension(element,
extName),
FileConstants.TAG_ATTRIBUTE);
if( ParseUtil.getJsfBoolean(tagAttrStr, true)){
return false;
}
// this property not a tag-attribute
Node parent = element.getParentNode();
parent.removeChild(element);
return true;
}
/**
* @param document
* @param extraPropsFile
*/
private void validateComplexCategories(Document document, File extraPropsFile){
Map<String, String> extraProps = getExtraProps(extraPropsFile);
Set<String> unreferencedKeys = new HashSet<String>(extraProps.keySet());
Element root = document.getDocumentElement();
for (Element i : ElementUtil.getChildren(root)) {
String name = i.getTagName();
boolean isComplexType = FileConstants.COMPLEX_TYPE.equals(name);
if (!isComplexType && !FileConstants.VALIDATOR.equals(name)
&& !FileConstants.CONVERTER.equals(name)) {
continue;
}
String category = null;
String extensionName = isComplexType? FileConstants.COMPLEX_EXTENSION : name+"-extension";
ElementIterator extension = ElementUtil.getExtension(i, extensionName);
if( null != extension ){
Element designerExtension = ElementUtil.getFirstChildElement(extension, "designer-extension");
if( null != designerExtension ){
category = ElementUtil.extractValue(designerExtension, "category");
}
}
if( null == category ){
continue;
}
if( category.length() <= 2 || '%' != category.charAt(0) || '%' != category.charAt(category.length()-1)){
System.err.println("Unresourced " + name + " category: "
+ category + " \t should use %complex-category."
+ category.toLowerCase() + "% \t category."
+ category.toLowerCase() + " = " + category);
continue;
}
String key = category.substring(1, category.length()-1);
String existing = extraProps.get(key);
if( null == existing ){
System.err.println("Category not in -extra.props file: " + key );
}else{
unreferencedKeys.remove(key);
}
}
if( unreferencedKeys.size() > 0 ){
for (String key : unreferencedKeys) {
System.err.println("Unused key in -extra.props file: " + key
+ " = " + extraProps.get(key));
}
}
}
/**
* @param extraPropsFile
* @return
*/
private Map<String, String> getExtraProps(File extraPropsFile) {
Map<String, String> extraProps = null;
if( null != extraPropsFile ){
Properties props = new Properties();
try {
props.load( new FileInputStream(extraPropsFile) );
}
catch (IOException e) {
throw new RuntimeException(e);
}
extraProps = asStringMap(props);
}
if( null == extraProps){
extraProps = Collections.emptyMap();
}
return extraProps;
}
/**
* @param props
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, String> asStringMap(Properties props) {
return (Map)props;
}
/**
* @param props
* @param name
* @return
* @throws IOException
*/
private String appendExtraProps(String props, File extraPropsFile) throws IOException {
if( null == extraPropsFile ){
return props;
}
String extraProps = readFileContents(extraPropsFile);
return props + extraProps;
}
/**
* @param props
* @param configFileName
* @return
*/
private File getExtraPropsFile(String configFileName, String extraPropsFolder) {
if(configFileName.startsWith("raw-")) {
configFileName = configFileName.replaceFirst("raw-", "");
}
String nameLessExtsn = configFileName.substring(0, configFileName.lastIndexOf('.'));
String extraFile = extraPropsFolder + nameLessExtsn + "-extra_en.properties";
File file = new File(extraFile);
if( ! file.exists() ){
return null;
}
return file;
}
/**
* @param documentElement
*/
private void removeElements(Element e, String elementName) {
NodeList kids = e.getChildNodes();
int i = 0;
while( i < kids.getLength() ){
Node kid = kids.item(i);
if( kid.getNodeType() == Node.ELEMENT_NODE ){
if( elementName.equals(kid.getNodeName()) ){
e.removeChild(kid);
continue;
}
removeElements((Element)kid, elementName);
}
i++;
}
}
/**
* @param str
* @return
* @throws IOException
*/
private String prependCopyrightXml(String str) throws IOException {
String copyrightFile = System.getProperty("user.dir")+ "/faces/COPYRIGHT.xml";
String copyright = readFileContents(new File(copyrightFile));
copyright = replaceYearPlaceholder(copyright);
return insertLine(str, copyright);
}
private static String THIS_YEAR = null;
private String replaceYearPlaceholder(String copyright) {
String yearPlaceholder = "${year}";
int placeholderIndex = copyright.indexOf(yearPlaceholder);
if( -1 != placeholderIndex ){
if( null == THIS_YEAR ){
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(System.currentTimeMillis());
int thisYear = cal.get(Calendar.YEAR);
String thisYearStr = Integer.toString(thisYear);
THIS_YEAR = thisYearStr;
}
copyright = copyright.substring(0, placeholderIndex)
+ THIS_YEAR
+ copyright.substring(placeholderIndex+yearPlaceholder.length());
}
return copyright;
}
/**
* @param str
* @param copyright
* @return
*/
private String insertLine(String str, String copyright) {
// the first line contains the
// <?xml version="1.0" encoding="UTF-8"?>
int firstNewline = str.indexOf(NEWLINE);
String firstLine = str.substring(0, firstNewline+NEWLINE.length());
str = str.substring(firstNewline+NEWLINE.length());
return firstLine + copyright + str;
}
/**
* @param str
* @return
* @throws IOException
*/
private String appendCopyrightProps(String str) throws IOException {
String copyrightFile = System.getProperty("user.dir")+ "/faces/" +
"COPYRIGHT.properties";
String copyright = readFileContents(new File(copyrightFile));
copyright = replaceYearPlaceholder(copyright);
return str + copyright;
}
private String readFileContents(File javaFilePath) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(javaFilePath));
StringBuffer result = new StringBuffer();
String line;
boolean isComment = false;
boolean isExtraPropsFile = false;
while ((line = in.readLine()) != null) {
if(javaFilePath.getName().endsWith("-extra_en.properties")) {
isExtraPropsFile = true;
} else {
isExtraPropsFile = false;
}
if(isExtraPropsFile && line.startsWith("#")) {
isComment = true;
} else {
isComment = false;
}
if(!isComment) {
result.append(line).append(Lines.NEWLINE);
}
}
in.close();
return result.toString();
}
/**
* @param e
* @return
*/
private void removeComments(Node e) {
NodeList kids = e.getChildNodes();
int i = 0;
while( i < kids.getLength() ){
Node kid = kids.item(i);
if( kid.getNodeType() == Node.COMMENT_NODE ){
e.removeChild(kid);
}else{
if( kid.getNodeType() == Node.ELEMENT_NODE ){
removeComments(kid);
}
i++;
}
}
}
public void removeXmlBaseAttributes(Element e, boolean isInlineMode) {
boolean removedPeerComments = false;
NodeList kids = e.getChildNodes();
int i = 0;
while( i < kids.getLength() ){
Node kid = kids.item(i);
if( kid.getNodeType() == Node.ELEMENT_NODE ){
Element childElement = (Element)kid;
String attr = childElement.getAttribute("xml:base");
if( null != attr ){
if( isInlineMode ){
String pathEnd = attr.substring(attr.lastIndexOf('/'));
if( pathEnd.length() < attr.length() ){
System.out.println("ConfigFlattener.removeXmlBaseAttributes() xml:base="+pathEnd);
childElement.setAttribute("xml:base", pathEnd);
}// else removedPeerComments is true
if( !removedPeerComments ){
removedPeerComments = true;
// remove Sun copyright headers that were included with these base elements
removeComments( e );
// removeComments will have made i invalid, so re-process all the peer nodes
i = 0;
continue;
}
}else{
childElement.removeAttribute("xml:base");
}
}
removeXmlBaseAttributes(childElement, isInlineMode);
}
i++;
}
}
}