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}