1 /* 2 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package build.tools.generatenimbus; 26 27 import javax.xml.stream.XMLInputFactory; 28 import javax.xml.stream.XMLStreamException; 29 import javax.xml.stream.XMLStreamReader; 30 import java.io.*; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 /** 35 * Generates the various Java artifacts based on a SynthModel. 36 * <p/> 37 * Generated source files are split up among two different locations. There are those source files that are meant to be 38 * edited (generally, only the LookAndFeel class itself) and those that are autogenerated (everything else). 39 * <p/> 40 * All autogenerated files are placed in "buildPackageRoot" and are package private. A LAF author (one who has access to 41 * the generated sources) will be able to access any of the generated classes. Those referencing the library, however, 42 * will only be able to access the main LookAndFeel class itself (since everything else is package private). 43 * 44 * @author Richard Bair 45 * @author Jasper Potts 46 */ 47 public class Generator { 48 private static Generator instance; 49 50 /** A map of variables that are used for variable substitution in the template files. */ 51 private Map<String, String> variables; 52 private boolean full = false; 53 private File buildPackageRoot; 54 private String packageNamePrefix; 55 private String lafName; 56 private SynthModel model; 57 58 /** 59 * MAIN APPLICATION 60 * <p/> 61 * This is for using the generator as part of the java build process 62 * 63 * @param args The commandline arguments 64 */ 65 public static void main(String[] args) throws Exception { 66 if (args.length == 0 || (args.length % 2) != 0) { 67 System.out.println("Usage: generator [-options]\n" + 68 " -full <true|false> True if we should build the whole LAF or false for building just states and painters.\n" + 69 " -skinFile <value> Path to the skin.laf file for the LAF to be generated from.\n" + 70 " -buildDir <value> The directory beneath which the build-controlled artifacts (such as the Painters) should\n" + 71 " be placed. This is the root directory beneath which the necessary packages and source\n" + 72 " files will be created.\n" + 73 " -resourcesDir <value> The resources directory containing templates and images.\n" + 74 " -packagePrefix <value> The package name associated with this synth look and feel. For example,\n" + 75 " \"org.mypackage.mylaf\"\n" + 76 " -lafName <value> The name of the laf, such as \"MyLAF\".\n"); 77 } else { 78 boolean full = false; 79 File skinFile = new File(System.getProperty("user.dir")); 80 File buildDir = new File(System.getProperty("user.dir")); 81 File resourcesDir = new File(System.getProperty("user.dir")); 82 String packagePrefix = "org.mypackage.mylaf"; 83 String lafName = "MyLAF"; 84 for (int i = 0; i < args.length; i += 2) { 85 String key = args[i].trim().toLowerCase(); 86 String value = args[i + 1].trim(); 87 if ("-full".equals(key)) { 88 full = Boolean.parseBoolean(value); 89 } else if ("-skinfile".equals(key)) { 90 skinFile = new File(value); 91 } else if ("-builddir".equals(key)) { 92 buildDir = new File(value); 93 } else if ("-resourcesdir".equals(key)) { 94 resourcesDir = new File(value); 95 } else if ("-packageprefix".equals(key)) { 96 packagePrefix = value; 97 } else if ("-lafname".equals(key)) { 98 lafName = value; 99 } 100 } 101 System.out.println("### GENERATING LAF CODE ################################"); 102 System.out.println(" full :" + full); 103 System.out.println(" skinFile :" + skinFile.getAbsolutePath()); 104 System.out.println(" buildDir :" + buildDir.getAbsolutePath()); 105 System.out.println(" resourcesDir :" + resourcesDir.getAbsolutePath()); 106 System.out.println(" packagePrefix :" +packagePrefix); 107 System.out.println(" lafName :" +lafName); 108 109 SynthModel model; 110 XMLInputFactory inputFactory = XMLInputFactory.newInstance(); 111 XMLStreamReader reader; 112 try( InputStream fis = new FileInputStream(skinFile); 113 InputStream is = new BufferedInputStream(fis)) { 114 reader = inputFactory.createXMLStreamReader(is); 115 model = new SynthModel(reader); 116 } 117 Generator.init(full, buildDir, packagePrefix, lafName, model); 118 Generator.getInstance().generate(); 119 } 120 } 121 122 /** 123 * Creates a new Generator, capable of outputting the source code artifacts related to a given SynthModel. It is 124 * capable of generating the one-time artifacts in addition to the regeneration of build-controlled artifacts. 125 * 126 * @param full True if we should build the whole LAF or false for building just states and painters. 127 * @param buildDir The directory beneath which the build-controlled artifacts (such as the Painters) should 128 * be placed. This is the root directory beneath which the necessary packages and source 129 * files will be created. 130 * @param srcDir The directory beneath which the normal user-controlled artifacts (such as the core 131 * LookAndFeel file) should be placed. These are one-time generated files. This is the root 132 * directory beneath which the necessary packages and source files will be created. 133 * @param packageNamePrefix The package name associated with this synth look and feel. For example, 134 * org.mypackage.mylaf 135 * @param lafName The name of the laf, such as MyLAF. 136 * @param model The actual SynthModel to base these generated files on. 137 */ 138 private Generator(boolean full, File buildDir, 139 String packageNamePrefix, String lafName, SynthModel model) { 140 this.full = full; 141 //validate the input variables 142 if (packageNamePrefix == null) { 143 throw new IllegalArgumentException("You must specify a package name prefix"); 144 } 145 if (buildDir == null) { 146 throw new IllegalArgumentException("You must specify the build directory"); 147 } 148 if (model == null) { 149 throw new IllegalArgumentException("You must specify the SynthModel"); 150 } 151 if (lafName == null) { 152 throw new IllegalArgumentException("You must specify the name of the look and feel"); 153 } 154 155 //construct the map which is used to do variable substitution of the template 156 //files 157 variables = new HashMap<String, String>(); 158 variables.put("PACKAGE", packageNamePrefix); 159 variables.put("LAF_NAME", lafName); 160 161 //generate and save references to the package-root directories. 162 //(That is, given the buildDir and srcDir, generate references to the 163 //org.mypackage.mylaf subdirectories) 164 buildPackageRoot = new File(buildDir, packageNamePrefix.replaceAll("\\.", "\\/")); 165 buildPackageRoot.mkdirs(); 166 167 //save the variables 168 this.packageNamePrefix = packageNamePrefix; 169 this.lafName = lafName; 170 this.model = model; 171 } 172 173 public static void init(boolean full, File buildDir, 174 String packageNamePrefix, String lafName, SynthModel model) { 175 instance = new Generator(full, buildDir, packageNamePrefix, lafName, model); 176 model.initStyles(); 177 } 178 179 public static Generator getInstance() { 180 return instance; 181 } 182 183 public static Map<String, String> getVariables() { 184 return new HashMap<String, String>(instance.variables); 185 } 186 187 public void generate() { 188 if (full) { 189 //create the LookAndFeel file 190 writeSrcFileImpl("LookAndFeel", variables, lafName + "LookAndFeel"); 191 192 writeSrcFileImpl("AbstractRegionPainter", variables); 193 writeSrcFileImpl("BlendingMode", variables); 194 writeSrcFileImpl("SynthPainterImpl", variables); 195 writeSrcFileImpl("IconImpl", variables, lafName + "Icon.java"); 196 writeSrcFileImpl("StyleImpl", variables, lafName + "Style.java"); 197 writeSrcFileImpl("Effect", variables); 198 writeSrcFileImpl("EffectUtils", variables); 199 writeSrcFileImpl("ShadowEffect", variables); 200 writeSrcFileImpl("DropShadowEffect", variables); 201 writeSrcFileImpl("InnerShadowEffect", variables); 202 writeSrcFileImpl("InnerGlowEffect", variables); 203 writeSrcFileImpl("OuterGlowEffect", variables); 204 writeSrcFileImpl("State", variables); 205 writeSrcFileImpl("ImageCache", variables); 206 writeSrcFileImpl("ImageScalingHelper", variables); 207 } 208 //next, populate the first set of ui defaults based on what is in the 209 //various palettes of the synth model 210 StringBuilder defBuffer = new StringBuilder(); 211 StringBuilder styleBuffer = new StringBuilder(); 212 model.write(defBuffer, styleBuffer, packageNamePrefix); 213 214 Map<String, String> vars = getVariables(); 215 vars.put("UI_DEFAULT_INIT", defBuffer.toString()); 216 vars.put("STYLE_INIT", styleBuffer.toString()); 217 writeSrcFile("Defaults", vars, lafName + "Defaults"); 218 } 219 220 private void writeSrcFileImpl(String name, Map<String, String> variables) { 221 writeSrcFileImpl(name, variables, name); 222 } 223 224 private void writeSrcFileImpl(String templateName, 225 Map<String, String> variables, String outputName) { 226 PrintWriter out = null; 227 try { 228 InputStream stream = getClass().getResourceAsStream( 229 "resources/" + templateName + ".template"); 230 TemplateReader in = new TemplateReader(variables, stream); 231 232 out = new PrintWriter(new File(buildPackageRoot, outputName + ".java")); 233 String line = in.readLine(); 234 while (line != null) { 235 out.println(line); 236 line = in.readLine(); 237 } 238 } catch (IOException e) { 239 throw new RuntimeException("IOException in writer", e); 240 } finally { 241 if (out != null) out.close(); 242 } 243 } 244 245 public static void writeSrcFile(String templateName, 246 Map<String, String> variables, String outputName) { 247 instance.writeSrcFileImpl(templateName, variables, outputName); 248 } 249 250 /** A BufferedReader implementation that automatically performs 251 * string replacements as needed. 252 */ 253 private static final class TemplateReader extends BufferedReader { 254 private Map<String, String> variables; 255 256 TemplateReader(Map<String, String> variables, InputStream template) { 257 super(new InputStreamReader(template)); 258 this.variables = variables; 259 } 260 261 @Override public String readLine() throws IOException { 262 return substituteVariables(super.readLine()); 263 } 264 265 private String substituteVariables(String input) { 266 if (input == null) return null; 267 for (Map.Entry<String, String> variable : variables.entrySet()) { 268 input = input.replace("${" + variable.getKey() + "}", variable.getValue()); 269 } 270 return input; 271 } 272 } 273 }