GenerateMessageFile.java revision cd845149d3f0adb85c23d522477ffb6d7c16cc7e
0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License, Version 1.0 only
0N/A * (the "License"). You may not use this file except in compliance
0N/A * with the License.
0N/A *
0N/A * You can obtain a copy of the license at
0N/A * trunk/opends/resource/legal-notices/OpenDS.LICENSE
0N/A * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
0N/A * See the License for the specific language governing permissions
0N/A * and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at
0N/A * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
0N/A * add the following below this CDDL HEADER, with the fields enclosed
0N/A * by brackets "[]" replaced with your own identifying information:
0N/A * Portions Copyright [yyyy] [name of copyright owner]
0N/A *
0N/A * CDDL HEADER END
0N/A *
0N/A *
0N/A * Copyright 2008 Sun Microsystems, Inc.
0N/A */
0N/Apackage org.opends.build.tools;
0N/A
0N/Aimport org.apache.tools.ant.Task;
0N/Aimport org.apache.tools.ant.BuildException;
0N/Aimport org.apache.tools.ant.Project;
0N/Aimport org.apache.tools.ant.Location;
0N/Aimport static org.opends.build.tools.Utilities.*;
0N/Aimport org.opends.messages.Category;
0N/Aimport org.opends.messages.Severity;
0N/Aimport org.opends.messages.MessageDescriptor;
0N/A
0N/Aimport java.io.File;
0N/Aimport java.io.FileInputStream;
0N/Aimport java.io.FileReader;
33N/Aimport java.io.BufferedReader;
0N/Aimport java.io.FileNotFoundException;
0N/Aimport java.io.FileOutputStream;
0N/Aimport java.io.InputStream;
0N/Aimport java.io.InputStreamReader;
0N/Aimport java.io.PrintWriter;
0N/Aimport java.io.DataOutputStream;
0N/Aimport java.io.IOException;
0N/Aimport java.util.Properties;
0N/Aimport java.util.List;
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.UnknownFormatConversionException;
0N/Aimport java.util.Calendar;
0N/Aimport java.util.Arrays;
0N/Aimport java.util.Locale;
0N/Aimport java.util.Map;
0N/Aimport java.util.TreeMap;
0N/Aimport java.util.HashSet;
0N/Aimport java.util.Set;
0N/Aimport java.util.EnumSet;
0N/Aimport java.util.regex.Matcher;
0N/Aimport java.util.regex.Pattern;
0N/A
0N/A/**
0N/A * Generates a Java class containing representations of messages
0N/A * found in a properties file.
0N/A */
0N/Apublic class GenerateMessageFile extends Task {
0N/A
0N/A private File source;
0N/A private File dest;
0N/A private boolean overwrite;
0N/A
0N/A static private final String MESSAGES_FILE_STUB =
0N/A "resource/Messages.java.stub";
0N/A
0N/A /*
0N/A * The registry filename is the result of the concatenation of the
0N/A * location of where the source are generated, the package name and the
0N/A * DESCRIPTORS_REG value.
0N/A */
0N/A static private String REGISTRY_FILE_NAME;
0N/A
0N/A static private final String DESCRIPTORS_REG = "descriptors.reg";
0N/A
0N/A /**
0N/A * Used to set a category for all messages in the property file.
0N/A * If set, the category for each message need not be encoded in
0N/A * the message's property file key.
0N/A */
0N/A static private final String GLOBAL_CATEGORY = "global.category";
0N/A
0N/A /**
33N/A * Used to set a severity for all messages in the property file.
33N/A * If set, the severity for each message need not be encoded in
33N/A * the message's property file key.
0N/A */
0N/A static private final String GLOBAL_SEVERITY = "global.severity";
0N/A
0N/A /**
0N/A * Used to set a category mask for all messages in the property
0N/A * file. If set, the category will automatically be assigned
0N/A * USER_DEFINED and the value of <code>GLOBAL_CATEGORY</code>
0N/A * will be ignored.
0N/A */
0N/A static private final String GLOBAL_CATEGORY_MASK = "global.mask";
0N/A
0N/A /**
0N/A * When true generates messages that have no ordinals.
0N/A */
0N/A static private final String GLOBAL_ORDINAL = "global.ordinal";
0N/A
0N/A /**
0N/A * When true and if the Java Web Start property is set use the class loader of
0N/A * the jar where the MessageDescriptor is contained to retrieve the
0N/A * ResourceBundle.
0N/A */
0N/A static private final String GLOBAL_USE_MESSAGE_JAR_IF_WEBSTART =
0N/A "global.use.message.jar.if.webstart";
0N/A
0N/A static private final Set<String> DIRECTIVE_PROPERTIES = new HashSet<String>();
0N/A static {
0N/A DIRECTIVE_PROPERTIES.add(GLOBAL_CATEGORY);
0N/A DIRECTIVE_PROPERTIES.add(GLOBAL_CATEGORY_MASK);
0N/A DIRECTIVE_PROPERTIES.add(GLOBAL_SEVERITY);
0N/A DIRECTIVE_PROPERTIES.add(GLOBAL_ORDINAL);
0N/A DIRECTIVE_PROPERTIES.add(GLOBAL_USE_MESSAGE_JAR_IF_WEBSTART);
0N/A }
0N/A
0N/A static private final String SPECIFIER_REGEX =
0N/A "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
0N/A
0N/A private final Pattern SPECIFIER_PATTERN = Pattern.compile(SPECIFIER_REGEX);
0N/A
0N/A /**
0N/A * Message giving formatting rules for string keys.
0N/A */
0N/A static public String KEY_FORM_MSG;
0N/A
0N/A static {
0N/A KEY_FORM_MSG = new StringBuilder()
0N/A .append(".\n\nOpenDS message property keys must be of the form\n\n")
0N/A .append("\t\'[CATEGORY]_[SEVERITY]_[DESCRIPTION]_[ORDINAL]\'\n\n")
0N/A .append("where\n\n")
0N/A .append("CATEGORY is one of ")
0N/A .append(EnumSet.allOf(Category.class))
0N/A .append("\n\nSEVERITY is one of ")
0N/A .append(Severity.getPropertyKeyFormSet().toString())
0N/A .append("\n\nDESCRIPTION is a descriptive string composed ")
0N/A .append("of uppercase character, digits and underscores ")
0N/A .append("describing the purpose of the message ")
0N/A .append("\n\nORDINAL is an integer between 0 and 65535 that is ")
0N/A .append("unique to other messages defined in this file.\n\n")
0N/A .append("You can relax the mandate for including the CATEGORY, ")
0N/A .append("SEVERITY, and/or ORDINAL by including one or more ")
0N/A .append("of the following respective property directives in your ")
0N/A .append("properties file: ")
0N/A .append(GLOBAL_CATEGORY)
0N/A .append(", ")
0N/A .append(GLOBAL_SEVERITY)
0N/A .append(", ")
0N/A .append(GLOBAL_ORDINAL)
0N/A .append("and setting their value appropriately.")
0N/A .toString();
0N/A }
0N/A
0N/A /*
0N/A * ISO_LANGUAGES contains all official supported languages for i18n
0N/A */
0N/A private static final List<String> ISO_LANGUAGES =
0N/A Arrays.asList(Locale.getISOLanguages());
0N/A /*
0N/A * ISO_COUNTRIES contains all official supported countries for i18n
0N/A */
0N/A private static final List<String> ISO_COUNTRIES =
0N/A Arrays.asList(Locale.getISOCountries());
0N/A
0N/A /*
0N/A * A Pattern instance that matches "<label>_<language>_<country>.properties"
0N/A * where <label> can be anything including '_'
0N/A * <language> a two characters code contained in the ISO_LANGUAGES list
0N/A * <country> a two characters code contained in the ISO_COUNTRIES list
0N/A */
0N/A private static final Pattern LANGUAGE_COUNTRY_MATCHER =
0N/A Pattern.compile("(.*)_([a-z]{2})_([A-Z]{2}).properties");
0N/A /*
63N/A * A Pattern instance that matches "<label>_<language>.properties"
0N/A * where <label> and <language> have same definition as above.
0N/A */
0N/A private static final Pattern LANGUAGE_MATCHER =
0N/A Pattern.compile("(.*)_([a-z]{2}).properties");
0N/A
0N/A /**
0N/A * Representation of a format specifier (for example %s).
0N/A */
0N/A private class FormatSpecifier {
0N/A
0N/A private String[] sa;
0N/A
0N/A /**
0N/A * Creates a new specifier.
0N/A * @param sa specifier components
0N/A */
0N/A FormatSpecifier(String[] sa) {
0N/A this.sa = sa;
0N/A }
0N/A
0N/A /**
0N/A * Indicates whether or not the specifier uses arguement
0N/A * indexes (for example 2$).
63N/A * @return boolean true if this specifier uses indexing
0N/A */
0N/A public boolean specifiesArgumentIndex() {
0N/A return this.sa[0] != null;
0N/A }
0N/A
63N/A /**
63N/A * Returns a java class associated with a particular formatter
0N/A * based on the conversion type of the specifier.
0N/A * @return Class for representing the type of arguement used
0N/A * as a replacement for this specifier.
63N/A */
63N/A public Class getSimpleConversionClass() {
0N/A Class c = null;
0N/A String sa4 = sa[4] != null ? sa[4].toLowerCase() : null;
0N/A String sa5 = sa[5] != null ? sa[5].toLowerCase() : null;
0N/A if ("t".equals(sa4)) {
0N/A c = Calendar.class;
0N/A } else if (
0N/A "b".equals(sa5)) {
0N/A c = Boolean.class;
0N/A } else if (
0N/A "h".equals(sa5)) {
63N/A c = Integer.class;
63N/A } else if (
63N/A "s".equals(sa5)) {
0N/A c = CharSequence.class;
0N/A } else if (
0N/A "c".equals(sa5)) {
0N/A c = Character.class;
0N/A } else if (
0N/A "d".equals(sa5) ||
0N/A "o".equals(sa5) ||
0N/A "x".equals(sa5) ||
0N/A "e".equals(sa5) ||
0N/A "f".equals(sa5) ||
0N/A "g".equals(sa5) ||
0N/A "a".equals(sa5)) {
0N/A c = Number.class;
0N/A } else if (
0N/A "n".equals(sa5) ||
0N/A "%".equals(sa5)) {
0N/A // ignore literals
0N/A }
0N/A return c;
0N/A }
0N/A
0N/A }
0N/A
0N/A /**
0N/A * Represents a message to be written into the messages files.
0N/A */
63N/A private class MessageDescriptorDeclaration {
0N/A
63N/A private MessagePropertyKey key;
63N/A private String formatString;
63N/A private List<FormatSpecifier> specifiers;
0N/A private List<Class> classTypes;
0N/A private String[] constructorArgs;
0N/A
0N/A /**
0N/A * Creates a parameterized instance.
0N/A * @param key of the message
0N/A * @param formatString of the message
0N/A */
0N/A public MessageDescriptorDeclaration(MessagePropertyKey key,
0N/A String formatString) {
0N/A this.key = key;
0N/A this.formatString = formatString;
0N/A this.specifiers = parse(formatString);
0N/A this.classTypes = new ArrayList<Class>();
0N/A for (FormatSpecifier f : specifiers) {
0N/A Class c = f.getSimpleConversionClass();
0N/A if (c != null) {
0N/A classTypes.add(c);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Gets the name of the Java class that will be used to represent
0N/A * this message's type.
0N/A * @return String representing the Java class name
0N/A */
0N/A public String getDescriptorClassDeclaration() {
0N/A StringBuilder sb = new StringBuilder();
0N/A if (useGenericMessageTypeClass()) {
0N/A sb.append(getShortClassName(MessageDescriptor.class));
0N/A sb.append(".");
0N/A sb.append(MessageDescriptor.DESCRIPTOR_CLASS_BASE_NAME);
0N/A sb.append("N");
0N/A } else {
0N/A sb.append(getShortClassName(MessageDescriptor.class));
0N/A sb.append(".");
0N/A sb.append(MessageDescriptor.DESCRIPTOR_CLASS_BASE_NAME);
0N/A sb.append(classTypes.size());
0N/A sb.append(getClassTypeVariables());
0N/A }
0N/A return sb.toString();
0N/A }
0N/A
0N/A /**
0N/A * Gets a string representing the message type class' variable
0N/A * information (for example '<String,Integer>') that is based on
0N/A * the type of arguments specified by the specifiers in this message.
0N/A * @return String representing the message type class parameters
0N/A */
0N/A public String getClassTypeVariables() {
0N/A StringBuilder sb = new StringBuilder();
0N/A if (classTypes.size() > 0) {
0N/A sb.append("<");
0N/A for (int i = 0; i < classTypes.size(); i++) {
0N/A Class c = classTypes.get(i);
0N/A if (c != null) {
0N/A sb.append(getShortClassName(c));
0N/A if (i < classTypes.size() - 1) {
0N/A sb.append(",");
0N/A }
0N/A }
0N/A }
0N/A sb.append(">");
0N/A }
0N/A return sb.toString();
0N/A }
0N/A
0N/A /**
0N/A * Gets the javadoc comments that will appear above the messages declaration
0N/A * in the messages file.
0N/A * @return String comment
0N/A */
0N/A public String getComment() {
0N/A StringBuilder sb = new StringBuilder();
0N/A sb.append(indent(1)).append("/**").append(EOL);
0N/A
0N/A // Unwrapped so that you can search through the descriptor
0N/A // file for a message and not have to worry about line breaks
0N/A String ws = formatString; // wrapText(formatString, 70);
0N/A
0N/A String[] sa = ws.split(EOL);
0N/A for (String s : sa) {
0N/A sb.append(indent(1)).append(" * ").append(s).append(EOL);
0N/A }
0N/A sb.append(indent(1)).append(" */").append(EOL);
0N/A return sb.toString();
0N/A }
0N/A
0N/A /**
0N/A * Sets the arguments that will be supplied in the declaration
0N/A * of the message.
0N/A * @param s array of string arguments that will be passed
0N/A * in the constructor
0N/A */
0N/A public void setConstructorArguments(String... s) {
0N/A this.constructorArgs = s;
0N/A }
0N/A
0N/A /**
0N/A * {@inheritDoc}
0N/A */
0N/A public String toString() {
0N/A StringBuilder sb = new StringBuilder();
0N/A sb.append(getComment());
0N/A sb.append(indent(1));
0N/A sb.append("public static final ");
0N/A sb.append(getDescriptorClassDeclaration());
0N/A sb.append(" ");
0N/A sb.append(key.getMessageDescriptorName());
0N/A sb.append(" =");
0N/A sb.append(EOL);
0N/A sb.append(indent(5));
0N/A sb.append("new ");
0N/A sb.append(getDescriptorClassDeclaration());
0N/A sb.append("(");
0N/A if (constructorArgs != null) {
0N/A for (int i = 0; i < constructorArgs.length; i++) {
0N/A sb.append(constructorArgs[i]);
0N/A if (i < constructorArgs.length - 1) {
0N/A sb.append(",");
0N/A }
0N/A }
0N/A sb.append(", ");
0N/A }
0N/A sb.append("getClassLoader()");
0N/A sb.append(");");
0N/A return sb.toString();
0N/A }
0N/A
0N/A /**
0N/A * Indicates whether the generic message type class should
0N/A * be used. In general this is when a format specifier is
0N/A * more complicated than we support or when the number of
0N/A * arguments exceeeds the number of specific message type
0N/A * classes (MessageType0, MessageType1 ...) that are defined.
0N/A * @return boolean indicating
0N/A */
0N/A private boolean useGenericMessageTypeClass() {
0N/A if (specifiers.size() > MessageDescriptor.DESCRIPTOR_MAX_ARG_HANDLER) {
0N/A return true;
0N/A } else if (specifiers != null) {
0N/A for (FormatSpecifier s : specifiers) {
63N/A if (s.specifiesArgumentIndex()) {
63N/A return true;
63N/A }
63N/A }
63N/A }
63N/A return false;
63N/A }
63N/A
63N/A /**
63N/A * Look for format specifiers in the format string.
63N/A * @param s format string
63N/A * @return list of format specifiers
63N/A */
63N/A private List<FormatSpecifier> parse(String s) {
63N/A List<FormatSpecifier> sl = new ArrayList<FormatSpecifier>();
63N/A Matcher m = SPECIFIER_PATTERN.matcher(s);
63N/A int i = 0;
63N/A while (i < s.length()) {
63N/A if (m.find(i)) {
63N/A // Anything between the start of the string and the beginning
63N/A // of the format specifier is either fixed text or contains
63N/A // an invalid format string.
63N/A if (m.start() != i) {
63N/A // Make sure we didn't miss any invalid format specifiers
63N/A checkText(s.substring(i, m.start()));
63N/A // Assume previous characters were fixed text
63N/A //al.add(new FixedString(s.substring(i, m.start())));
63N/A }
63N/A
63N/A // Expect 6 groups in regular expression
63N/A String[] sa = new String[6];
63N/A for (int j = 0; j < m.groupCount(); j++) {
63N/A sa[j] = m.group(j + 1);
63N/A }
63N/A sl.add(new FormatSpecifier(sa));
63N/A i = m.end();
63N/A } else {
63N/A // No more valid format specifiers. Check for possible invalid
63N/A // format specifiers.
63N/A checkText(s.substring(i));
63N/A // The rest of the string is fixed text
0N/A //al.add(new FixedString(s.substring(i)));
0N/A break;
0N/A }
0N/A }
0N/A return sl;
0N/A }
0N/A
0N/A private void checkText(String s) {
0N/A int idx;
0N/A // If there are any '%' in the given string, we got a bad format
0N/A // specifier.
0N/A if ((idx = s.indexOf('%')) != -1) {
0N/A char c = (idx > s.length() - 2 ? '%' : s.charAt(idx + 1));
0N/A throw new UnknownFormatConversionException(String.valueOf(c));
0N/A }
0N/A }
0N/A
0N/A }
0N/A
0N/A /**
0N/A * Sets the source of the messages.
0N/A * @param source File representing the properties
0N/A * file containing messages
0N/A */
0N/A public void setSourceProps(File source) {
0N/A this.source = source;
0N/A }
0N/A
0N/A /**
0N/A * Sets the file that will be generated containing
0N/A * declarations of messages from <code>source</code>.
0N/A * @param dest File destination
0N/A */
0N/A public void setDestJava(File dest) {
0N/A this.dest = dest;
0N/A
0N/A /*
0N/A * Set the descriptors.reg pathname to the same directory as the one used
0N/A * to generate files and ensure all messages are generated in one place.
0N/A */
0N/A String projectBase = null;
0N/A try {
0N/A projectBase = getProject().getBaseDir().getCanonicalPath();
0N/A } catch( java.io.IOException e) {
0N/A throw new BuildException("Error processing " + dest +
0N/A ": unable to retrieve project's directory of ant's project (" +
0N/A e + ")");
0N/A }
0N/A
0N/A String registry = dest.getAbsolutePath();
0N/A // strip project directory prefix and replace properties filename with
0N/A // $DESCRIPTORS_REG
0N/A registry = registry.substring(projectBase.length()+1,
0N/A registry.lastIndexOf(File.separator)+1)
0N/A .concat(DESCRIPTORS_REG);
0N/A
0N/A if ( REGISTRY_FILE_NAME == null ) {
0N/A REGISTRY_FILE_NAME = registry;
0N/A } else {
0N/A if ( ! REGISTRY_FILE_NAME.equals(registry) ) {
0N/A // multiple messages are generated in several packages
0N/A StringBuilder sb = new StringBuilder();
0N/A // full pathname of $REGISTRY_FILE_NAME
0N/A sb.append(projectBase)
0N/A .append(File.separator)
0N/A .append(REGISTRY_FILE_NAME);
0N/A // change from generated directory to properties files directory
0N/A sb.replace(0,
0N/A getProject().getProperty("msg.javagen.dir").length(),
0N/A getProject().getProperty("msg.dir"));
0N/A // replace properties filename with source filename
0N/A sb.replace(sb.lastIndexOf(File.separator)+1,
0N/A sb.length(),
0N/A source.getName());
0N/A throw new BuildException("Error processing " + dest +
0N/A ": all messages must be located in the same package thus " +
0N/A "name of the source file should be " + sb);
0N/A
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Indicates when true that an existing destination
0N/A * file will be overwritten.
0N/A * @param o boolean where true means overwrite
0N/A */
0N/A public void setOverwrite(boolean o) {
0N/A this.overwrite = o;
0N/A }
0N/A
0N/A /**
0N/A * {@inheritDoc}
0N/A */
0N/A @Override
0N/A public void execute() throws BuildException {
0N/A
0N/A if ( this.dest == null ) {
0N/A // this is an example-plugin call:
0N/A // - check the source file is not a localization
0N/A // - guess the destination filename from source filename
0N/A String sourcefilename = source.getAbsolutePath();
0N/A int filenameIndex = sourcefilename.lastIndexOf(File.separator)+1;
0N/A String pathname = sourcefilename.substring(0, filenameIndex);
0N/A String filename = sourcefilename.substring(filenameIndex);
0N/A
0N/A /*
0N/A * Make sure only <label>.properties are generated thus avoiding to
0N/A * generate messages for localized properties files.
0N/A */
0N/A Matcher matcher = LANGUAGE_COUNTRY_MATCHER.matcher(filename);
0N/A if ( matcher.find() ) {
0N/A if ( ISO_LANGUAGES.contains(matcher.group(2))
0N/A && ISO_COUNTRIES.contains(matcher.group(3)) ) {
0N/A // do not generate message for <label>_<language>_<country>.properties
0N/A return;
0N/A }
0N/A }
0N/A
0N/A matcher = LANGUAGE_MATCHER.matcher(filename);
0N/A if ( matcher.find() ) {
0N/A if ( ISO_LANGUAGES.contains(matcher.group(2)) ) {
0N/A // do not generate message for <label>_<language>.properties
0N/A return;
0N/A }
0N/A }
0N/A // filename without ".properties"
0N/A filename = filename.substring(0, filename.length()-11);
0N/A // change to src-generated directory keeping package name
0N/A pathname = pathname.replace(getProject().getProperty("msg.dir"),
0N/A getProject().getProperty("msg.javagen.dir"));
0N/A
0N/A // append characters from filename to pathname starting with an uppercase
0N/A // letter, ignoring '_' and uppering all characters prefixed with "_"
0N/A StringBuilder sb = new StringBuilder(pathname);
0N/A boolean upperCaseNextChar = true;
0N/A for(char c : filename.toCharArray()) {
0N/A if ( c == '_' ) {
0N/A upperCaseNextChar = true;
0N/A continue;
0N/A }
0N/A if ( upperCaseNextChar ) {
0N/A sb.append(Character.toUpperCase(c));
0N/A upperCaseNextChar = false;
0N/A } else {
0N/A sb.append(c);
0N/A }
0N/A }
0N/A sb.append("Messages.java");
0N/A
0N/A setDestJava(new File(sb.toString()));
0N/A }
0N/A
0N/A BufferedReader stubReader = null;
0N/A PrintWriter destWriter = null;
0N/A try {
0N/A
0N/A // Decide whether to generate messages based on modification
0N/A // times and print status messages.
0N/A if (!source.exists()) {
0N/A throw new BuildException("file " + source.getName() +
0N/A " does not exist");
0N/A }
0N/A if (dest.exists()) {
0N/A if (this.overwrite || source.lastModified() > dest.lastModified()) {
0N/A dest.delete();
0N/A log("Regenerating " + dest.getName() + " from " + source.getName());
0N/A } else {
0N/A log(dest.getName() + " is up to date");
0N/A return;
0N/A }
0N/A } else {
0N/A File javaGenDir = dest.getParentFile();
0N/A if (!javaGenDir.exists()) {
0N/A javaGenDir.mkdirs();
0N/A }
0N/A log("Generating " + dest.getName() + " from " + source.getName());
0N/A }
0N/A
0N/A stubReader = new BufferedReader(new InputStreamReader(getStubFile(),
0N/A "UTF-8"));
0N/A destWriter = new PrintWriter(dest, "UTF-8");
0N/A String stubLine;
0N/A Properties properties = new Properties();
0N/A properties.load(new FileInputStream(source));
0N/A while (null != (stubLine = stubReader.readLine())) {
0N/A if (stubLine.contains("${MESSAGES}")) {
0N/A Integer globalOrdinal = null;
0N/A String go = properties.getProperty(GLOBAL_ORDINAL);
0N/A if (go != null) {
0N/A globalOrdinal = new Integer(go);
0N/A }
0N/A
0N/A // Determine the value of the global category/mask if set
0N/A Integer globalMask = null;
0N/A Category globalCategory = null;
0N/A String gms = properties.getProperty(GLOBAL_CATEGORY_MASK);
0N/A if (gms != null) {
0N/A globalMask = Integer.parseInt(gms);
0N/A globalCategory = Category.USER_DEFINED;
0N/A } else {
0N/A String gcs = properties.getProperty(GLOBAL_CATEGORY);
0N/A if (gcs != null) {
0N/A globalCategory = Category.valueOf(gcs);
0N/A }
0N/A }
0N/A
0N/A // Determine the value of the global severity
0N/A Severity globalSeverity = null;
0N/A String gss = properties.getProperty(GLOBAL_SEVERITY);
0N/A if (gss != null) {
0N/A globalSeverity = Severity.parseString(gss);
0N/A }
0N/A
0N/A Map<MessagePropertyKey,String> keyMap =
0N/A new TreeMap<MessagePropertyKey,String>();
0N/A
0N/A for (Object propO : properties.keySet()) {
0N/A String propKey = propO.toString();
0N/A try {
0N/A if (!DIRECTIVE_PROPERTIES.contains(propKey)) {
0N/A MessagePropertyKey key =
0N/A MessagePropertyKey.parseString(
0N/A propKey,
0N/A globalCategory == null,
0N/A globalSeverity == null,
0N/A globalOrdinal == null);
39N/A String formatString = properties.getProperty(propKey);
39N/A keyMap.put(key, formatString);
0N/A }
0N/A } catch (IllegalArgumentException iae) {
0N/A throw new BuildException(
0N/A "ERROR: invalid property key " + propKey +
0N/A ": " + iae.getMessage() +
0N/A KEY_FORM_MSG);
0N/A }
0N/A }
0N/A
0N/A int usesOfGenericDescriptor = 0;
0N/A
0N/A Category firstCategory = null;
0N/A Set<Integer> usedOrdinals = new HashSet<Integer>();
0N/A for (MessagePropertyKey key : keyMap.keySet()) {
0N/A String formatString = keyMap.get(key);
0N/A MessageDescriptorDeclaration message =
0N/A new MessageDescriptorDeclaration(key, formatString);
0N/A
0N/A Category c = (globalCategory != null ?
0N/A globalCategory : key.getCategory());
0N/A
0N/A // Check that this category is the same as all the
0N/A // others in this file. Maybe this should be an error?
0N/A if (firstCategory != null) {
0N/A if (!firstCategory.equals(c)) {
0N/A log("WARNING: multiple categories defined in " + source);
0N/A }
0N/A } else {
0N/A firstCategory = c;
0N/A }
0N/A
0N/A Severity s = (globalSeverity != null ?
0N/A globalSeverity : key.getSeverity());
0N/A
0N/A if (c == null) {
0N/A throw new BuildException(
0N/A "No category could be assigned to message " +
0N/A key + ". The category " +
0N/A "must either be encoded in the property key or " +
0N/A "or must be set by including the property " +
0N/A GLOBAL_CATEGORY + " in the properties file" +
0N/A KEY_FORM_MSG);
0N/A }
0N/A
0N/A if (c == null) {
0N/A throw new BuildException(
0N/A "No severity could be assigned to message " +
0N/A key + ". The severity " +
0N/A "must either be encoded in the property key or " +
0N/A "or must be set by including the property " +
0N/A GLOBAL_SEVERITY + " in the properties file" +
0N/A KEY_FORM_MSG);
0N/A }
0N/A
0N/A if (globalOrdinal == null) {
0N/A Integer ordinal = key.getOrdinal();
0N/A if (usedOrdinals.contains(ordinal)) {
0N/A throw new BuildException(
0N/A "The ordinal value \'" + ordinal + "\' in key " +
0N/A key + " has been previously defined in " +
0N/A source + KEY_FORM_MSG);
0N/A } else {
0N/A usedOrdinals.add(ordinal);
0N/A }
0N/A }
0N/A
0N/A message.setConstructorArguments(
0N/A "BASE",
0N/A quote(key.toString()),
0N/A globalMask != null ? globalMask.toString() : c.name(),
0N/A s.name(),
0N/A globalOrdinal != null ?
0N/A globalOrdinal.toString() :
0N/A key.getOrdinal().toString()
0N/A );
0N/A destWriter.println(message.toString());
0N/A destWriter.println();
0N/A
0N/A // Keep track of when we use the generic descriptor
0N/A // so that we can report it later
0N/A if (message.useGenericMessageTypeClass()) {
0N/A usesOfGenericDescriptor++;
0N/A }
0N/A }
0N/A
0N/A log(" Message Generated:" + keyMap.size(), Project.MSG_VERBOSE);
0N/A log(" MessageDescriptor.ArgN:" + usesOfGenericDescriptor,
0N/A Project.MSG_VERBOSE);
0N/A
0N/A } else {
0N/A stubLine = stubLine.replace("${PACKAGE}", getPackage());
0N/A stubLine = stubLine.replace("${CLASS_NAME}",
0N/A dest.getName().substring(0, dest.getName().length() -
0N/A ".java".length()));
0N/A stubLine = stubLine.replace("${BASE}", getBase());
0N/A
0N/A String useMessageJarIfWebstart =
0N/A properties.getProperty(GLOBAL_USE_MESSAGE_JAR_IF_WEBSTART);
0N/A if ((useMessageJarIfWebstart != null) &&
0N/A ("true".equalsIgnoreCase(useMessageJarIfWebstart) ||
0N/A "on".equalsIgnoreCase(useMessageJarIfWebstart) ||
0N/A "true".equalsIgnoreCase(useMessageJarIfWebstart)))
0N/A {
0N/A useMessageJarIfWebstart = "true";
0N/A }
0N/A else
0N/A {
0N/A useMessageJarIfWebstart = "false";
0N/A }
0N/A stubLine = stubLine.replace("${USE_MESSAGE_JAR_IF_WEBSTART}",
0N/A useMessageJarIfWebstart);
0N/A destWriter.println(stubLine);
0N/A }
0N/A }
0N/A
0N/A registerMessageDescriptor(getMessageDescriptorFullClassName());
0N/A
0N/A stubReader.close();
0N/A destWriter.close();
0N/A
0N/A } catch (Exception e) {
0N/A // Don't leave a malformed file laying around. Delete
0N/A // it so it will be forced to be regenerated.
0N/A if (dest.exists()) {
0N/A dest.deleteOnExit();
0N/A }
0N/A e.printStackTrace();
0N/A throw new BuildException("Error processing " + source +
0N/A ": " + e.getMessage());
0N/A } finally {
0N/A if (stubReader != null) {
0N/A try {
0N/A stubReader.close();
0N/A } catch (Exception e){
0N/A // ignore
0N/A }
0N/A }
0N/A if (destWriter != null) {
0N/A try {
0N/A destWriter.close();
0N/A } catch (Exception e){
0N/A // ignore
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A private String getMessageDescriptorFullClassName() {
0N/A return getPackage() + "." + getMessageDescriptorClassName();
0N/A }
0N/A
0N/A private String getMessageDescriptorClassName() {
0N/A return dest.getName().substring(
0N/A 0, dest.getName().length() - ".java".length());
0N/A }
0N/A
0N/A private String getBase() {
0N/A String srcPath = unixifyPath(source.getAbsolutePath());
0N/A String base = srcPath.substring(srcPath.lastIndexOf("/") + 1,
0N/A srcPath.length() - ".properties".length());
0N/A return base;
0N/A }
0N/A
0N/A private String getPackage() {
0N/A String destPath = unixifyPath(dest.getAbsolutePath());
0N/A String msgJavaGenDir = unixifyPath(
0N/A getProject().getProperty("msg.javagen.dir"));
0N/A String c = destPath.substring(msgJavaGenDir.length()+1);
0N/A c = c.replace('/', '.');
0N/A c = c.substring(0, c.lastIndexOf(".")); // strip .java
0N/A c = c.substring(0, c.lastIndexOf(".")); // strip class name
0N/A return c;
0N/A }
0N/A
0N/A static private String indent(int indent) {
0N/A char[] blankArray = new char[2 * indent];
0N/A Arrays.fill(blankArray, ' ');
0N/A return new String(blankArray);
0N/A }
0N/A
0N/A static private String quote(String s) {
0N/A return new StringBuilder()
0N/A .append("\"")
0N/A .append(s)
0N/A .append("\"")
0N/A .toString();
0N/A }
0N/A
static private String getShortClassName(Class c) {
String name;
String fqName = c.getName();
int i = fqName.lastIndexOf('.');
if (i > 0) {
name = fqName.substring(i + 1);
} else {
name = fqName;
}
return name;
}
/**
* Writes a record in the messages registry for the specifed
* class name.
* @param descClassName name of the message descriptor class
* @return true if the class was acutally added to the registry;
* false indicates that the class was already present.
* @throws IOException if there is a problem with the file I/O
*/
private boolean registerMessageDescriptor(String descClassName)
throws IOException
{
boolean classAdded = false;
File registry = getRegistryFile();
if (!isDescriptorRegistered(descClassName)) {
FileOutputStream file = new FileOutputStream(registry,true);
DataOutputStream out = new DataOutputStream(file);
out.writeBytes(descClassName);
out.writeBytes("\n");
out.flush();
out.close();
}
return classAdded;
}
private boolean isDescriptorRegistered(String descClassName)
throws IOException
{
boolean isRegistered = false;
BufferedReader reader = new BufferedReader(
new FileReader(getRegistryFile()));
String line;
while(null != (line = reader.readLine())) {
if (line.trim().equals(descClassName.trim())) {
isRegistered = true;
break;
}
}
return isRegistered;
}
private File getRegistryFile() throws IOException {
File registry = new File(getProjectBase(), REGISTRY_FILE_NAME);
if (!registry.exists()) {
File parent = registry.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
registry.createNewFile();
}
return registry;
}
private File getProjectBase() {
File projectBase;
// Get the path to build.xml and return the parent
// directory else just return the working directory.
Location l = getLocation();
String fileName = l.getFileName();
if (fileName != null) {
File f = new File(fileName);
projectBase = f.getParentFile();
} else {
projectBase = new File(System.getProperty("user.dir"));
}
return projectBase;
}
private String unixifyPath(String path) {
return path.replace("\\", "/");
}
/*
* Returns the stub file ("resource/Messages.java.stub") from the appropriate
* location: ant or jar file.
*/
private InputStream getStubFile() {
InputStream result = null;
File stub = new File(getProjectBase(), MESSAGES_FILE_STUB);
if ( stub.exists() ) {
// this is the OpenDS's ant project calling
// Stub is located at OPENDS_ROOT/resource/Messages.java.stub
try {
result = new FileInputStream(stub);
} catch (FileNotFoundException e) {
// should neven happen
throw new BuildException("Unable to load template " +
MESSAGES_FILE_STUB + ": " + e.getMessage());
}
} else {
// this is the example plugin's ant project calling
// Stub is located at build-tools.jar:resource/Messages.java.stub
result = getClass().getResourceAsStream(MESSAGES_FILE_STUB);
}
return result;
}
/**
* For testing.
* @param args from command line
*/
public static void main(String[] args) {
File source = new File("src/messages/messages/tools.properties");
File dest = new File("/tmp/org/opends/XXX.java");
GenerateMessageFile gmf = new GenerateMessageFile();
gmf.setOverwrite(true);
gmf.setDestJava(dest);
gmf.setSourceProps(source);
gmf.execute();
}
}