001package org.andromda.andromdapp; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.IOException; 006import java.io.InputStreamReader; 007import java.net.MalformedURLException; 008import java.net.URL; 009import java.util.ArrayList; 010import java.util.Iterator; 011import java.util.LinkedHashMap; 012import java.util.List; 013import java.util.Map; 014import java.util.Set; 015import org.andromda.core.common.AndroMDALogger; 016import org.andromda.core.common.ResourceFinder; 017import org.andromda.core.common.ResourceUtils; 018import org.andromda.core.common.XmlObjectFactory; 019import org.apache.commons.lang.StringUtils; 020 021/** 022 * Represents an instance of the AndroMDA application 023 * generator. 024 * 025 * @author Chad Brandon 026 */ 027public class AndroMDApp 028{ 029 /** 030 * An AndroMDApp configuration that contains some internal configuration information (like the AndroMDA 031 * version, and other common properties). 032 */ 033 private static final String INTERNAL_CONFIGURATION_URI = "META-INF/andromdapp/configuration.xml"; 034 035 /** 036 * Runs the AndroMDApp generation process. 037 */ 038 public void run() 039 { 040 try 041 { 042 AndroMDALogger.initialize(); 043 final URL internalConfiguration = ResourceUtils.getResource(INTERNAL_CONFIGURATION_URI); 044 if (internalConfiguration == null) 045 { 046 throw new AndroMDAppException("No configuration could be loaded from --> '" + 047 INTERNAL_CONFIGURATION_URI + '\''); 048 } 049 this.addConfigurationUri(internalConfiguration.toString()); 050 this.initialize(); 051 this.chooseTypeAndRun(true); 052 } 053 catch (final Throwable throwable) 054 { 055 if (throwable instanceof AndroMDAppException) 056 { 057 throw (AndroMDAppException)throwable; 058 } 059 throw new AndroMDAppException(throwable); 060 } 061 } 062 063 /** 064 * The name of the AndroMDApp descriptor. 065 */ 066 static final String DESCRIPTOR = "andromdapp.xml"; 067 068 /** 069 * The directory in which the descriptors are kept. 070 */ 071 private static final String DESCRIPTOR_DIRECTORY = "META-INF/andromdapp"; 072 073 /** 074 * All types of discovered AndroMDApps 075 */ 076 private final Map<String, AndroMDAppType> types = new LinkedHashMap<String, AndroMDAppType>(); 077 078 /** 079 * Performs any required initialization. 080 * @throws Exception 081 */ 082 private void initialize() 083 throws Exception 084 { 085 final URL[] descriptorDirectories = ResourceFinder.findResources(DESCRIPTOR_DIRECTORY); 086 if (descriptorDirectories != null && descriptorDirectories.length > 0) 087 { 088 final int numberOfDescriptorDirectories = descriptorDirectories.length; 089 for (int ctr = 0; ctr < numberOfDescriptorDirectories; ctr++) 090 { 091 final List<String> directoryContents = 092 ResourceUtils.getDirectoryContents( 093 descriptorDirectories[ctr], 094 true, 095 null); 096 for (final String uri : directoryContents) 097 { 098 if (uri.replaceAll(".*(\\\\+|/)","").equals(DESCRIPTOR)) 099 { 100 final XmlObjectFactory factory = XmlObjectFactory.getInstance(AndroMDApp.class); 101 final URL descriptorUri = ResourceUtils.toURL(uri); 102 final AndroMDAppType andromdapp = (AndroMDAppType) factory.getObject(descriptorUri); 103 andromdapp.setResource(descriptorUri); 104 final String type = andromdapp.getType(); 105 AndroMDALogger.info("discovered andromdapp type --> '" + type + '\''); 106 this.types.put( 107 type, 108 andromdapp); 109 } 110 } 111 } 112 } 113 } 114 115 /** 116 * Stores the optional configuration instance. 117 */ 118 private final List<Configuration> configurations = new ArrayList<Configuration>(); 119 120 /** 121 * Adds the URI for an optional configuration These are useful if you want 122 * to preconfigure the andromdapp when any properties, etc. 123 * 124 * @param configurationUri the URI to the configuration. 125 */ 126 public void addConfigurationUri(final String configurationUri) 127 { 128 if (StringUtils.isNotBlank(configurationUri)) 129 { 130 final XmlObjectFactory factory = XmlObjectFactory.getInstance(Configuration.class); 131 final URL configurationUrl = ResourceUtils.toURL(configurationUri); 132 if (configurationUrl == null) 133 { 134 throw new AndroMDAppException("configuriationUri is invalid --> '" + configurationUri + '\''); 135 } 136 this.configurations.add((Configuration)factory.getObject(configurationUrl)); 137 } 138 } 139 140 /** 141 * Adds the configuration contents stored as a String. 142 * 143 * @param configuration the configuration contents as a string. 144 */ 145 public void addConfiguration(final String configuration) 146 { 147 if (StringUtils.isNotBlank(configuration)) 148 { 149 final XmlObjectFactory factory = XmlObjectFactory.getInstance(Configuration.class); 150 this.configurations.add((Configuration)factory.getObject(configuration)); 151 } 152 } 153 154 /** 155 * Prompts the user to choose the type of application, and then runs that AndroMDAppType. 156 * @throws Exception 157 */ 158 private List<File> chooseTypeAndRun(final boolean write) 159 throws Exception 160 { 161 if (this.types.isEmpty()) 162 { 163 throw new AndroMDAppException("No '" + DESCRIPTOR + "' descriptor files could be found"); 164 } 165 final Map<String, String> properties = new LinkedHashMap<String, String>(); 166 for (Configuration configuration : this.configurations) 167 { 168 properties.putAll(configuration.getAllProperties()); 169 } 170 final String applicationType = properties.get(APPLICATION_TYPE); 171 final Set<String> validTypes = this.types.keySet(); 172 AndroMDAppType andromdapp = this.types.get(applicationType); 173 if (andromdapp == null) 174 { 175 if (this.types.size() > 1) 176 { 177 final StringBuilder typesChoice = new StringBuilder("["); 178 for (final Iterator<String> iterator = validTypes.iterator(); iterator.hasNext();) 179 { 180 final String type = iterator.next(); 181 typesChoice.append(type); 182 if (iterator.hasNext()) 183 { 184 typesChoice.append(", "); 185 } 186 } 187 typesChoice.append(']'); 188 this.printText("Please choose the type of application to generate " + typesChoice); 189 String selectedType = this.readLine(); 190 while (!this.types.containsKey(selectedType)) 191 { 192 selectedType = this.readLine(); 193 } 194 andromdapp = this.types.get(selectedType); 195 } 196 else if (!this.types.isEmpty()) 197 { 198 andromdapp = this.types.entrySet().iterator().next().getValue(); 199 } 200 } 201 202 if (andromdapp == null) 203 { 204 throw new AndroMDAppException("AndromdaPP is null " + this.types.entrySet()); 205 } 206 andromdapp.setConfigurations(this.configurations); 207 andromdapp.initialize(); 208 209 final Map templateContext = andromdapp.getTemplateContext(); 210 211 final XmlObjectFactory factory = XmlObjectFactory.getInstance(AndroMDApp.class); 212 final String contents = andromdapp.promptUser(); 213 // - evaluate all properties in the descriptor and recreate the AndroMDAppType 214 andromdapp = (AndroMDAppType)factory.getObject(contents); 215 andromdapp.setConfigurations(this.configurations); 216 andromdapp.addToTemplateContext(templateContext); 217 return andromdapp.processResources(write); 218 } 219 220 /** 221 * Identifies the AndroMDApp type (used to override the prompting of the type). 222 */ 223 private static final String APPLICATION_TYPE = "andromdappType"; 224 225 /** 226 * Removes all structure generated from the previous run. 227 */ 228 public void clean() 229 { 230 try 231 { 232 AndroMDALogger.initialize(); 233 this.initialize(); 234 final List<File> list = this.chooseTypeAndRun(false); 235 for (final File file : list) 236 { 237 this.deleteFile(file); 238 } 239 } 240 catch (final Throwable throwable) 241 { 242 if (throwable instanceof AndroMDAppException) 243 { 244 throw (AndroMDAppException)throwable; 245 } 246 throw new AndroMDAppException(throwable); 247 } 248 } 249 250 /** 251 * Deletes the given file and any empty parent directories 252 * that the file might be contained within. 253 * 254 * @param file the file to remove. 255 * @throws MalformedURLException 256 */ 257 private void deleteFile(final File file) throws MalformedURLException 258 { 259 if (file != null && file.exists()) 260 { 261 final File[] children = file.listFiles(); 262 if (children == null || children.length == 0) 263 { 264 if (file.delete()) 265 { 266 AndroMDALogger.info("Removed: '" + file.toURI().toURL() + '\''); 267 } 268 this.deleteFile(file.getParentFile()); 269 } 270 } 271 } 272 273 /** 274 * Prints text to the console. 275 * 276 * @param text the text to print to the console; 277 */ 278 private void printText(final String text) 279 { 280 System.out.println(); // NOPMD - have to print to console prompt 281 System.out.println(text); // NOPMD - have to print to console prompt 282 System.out.flush(); 283 } 284 285 /** 286 * Reads a line from standard input and returns the value. 287 * 288 * @return the value read from standard input. 289 */ 290 private String readLine() 291 { 292 final BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); 293 String inputString = null; 294 try 295 { 296 inputString = input.readLine(); 297 } 298 catch (final IOException exception) 299 { 300 AndroMDALogger.info("AndroMDApp Error reading inputLine from System.in"); 301 } 302 return StringUtils.trimToNull(inputString); 303 } 304}