001    /*
002     * Copyright 2011 Christian Kumpe http://kumpe.de/christian/java
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package de.kumpe.hadooptimizer.examples.painting;
017    
018    import static java.lang.Double.*;
019    import static java.lang.String.*;
020    
021    import java.io.IOException;
022    import java.io.OutputStream;
023    import java.util.Locale;
024    
025    import javax.xml.parsers.DocumentBuilder;
026    import javax.xml.parsers.DocumentBuilderFactory;
027    import javax.xml.parsers.ParserConfigurationException;
028    import javax.xml.transform.Transformer;
029    import javax.xml.transform.TransformerConfigurationException;
030    import javax.xml.transform.TransformerException;
031    import javax.xml.transform.TransformerFactory;
032    import javax.xml.transform.TransformerFactoryConfigurationError;
033    import javax.xml.transform.dom.DOMSource;
034    import javax.xml.transform.stream.StreamResult;
035    import javax.xml.xpath.XPath;
036    import javax.xml.xpath.XPathConstants;
037    import javax.xml.xpath.XPathExpressionException;
038    import javax.xml.xpath.XPathFactory;
039    
040    import org.apache.commons.cli.Options;
041    import org.apache.hadoop.conf.Configuration;
042    import org.apache.hadoop.fs.FileSystem;
043    import org.apache.hadoop.fs.Path;
044    import org.apache.hadoop.io.LongWritable;
045    import org.apache.hadoop.io.ObjectWritable;
046    import org.apache.hadoop.io.SequenceFile;
047    import org.apache.hadoop.io.Text;
048    import org.apache.hadoop.mapreduce.Job;
049    import org.apache.hadoop.mapreduce.Mapper;
050    import org.apache.hadoop.mapreduce.Reducer;
051    import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
052    import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
053    import org.w3c.dom.DOMException;
054    import org.w3c.dom.Document;
055    import org.w3c.dom.Element;
056    import org.xml.sax.SAXException;
057    
058    import de.kumpe.hadooptimizer.examples.Example;
059    import de.kumpe.hadooptimizer.hadoop.IndividualInputFormat;
060    
061    /**
062     * The {@link SvgRenderer} uses a MapReduce-Job to convert the results.txt of a
063     * {@link Kit} run into a SVG-Image of every 1000 evolution-cycle's best result.
064     * <p>
065     * It also writes these results into a {@link SequenceFile} with the cycle as a
066     * {@link LongWritable} key and the values as an {@code double[]} wrapped into
067     * an {@link ObjectWritable}.
068     * <p>
069     * The results.txt has to be given as the first argument.
070     * 
071     * @author <a href="http://kumpe.de/christian/java">Christian Kumpe</a>
072     */
073    public class SvgRenderer extends Example {
074            private static final String OUTPUT_PATH = "de.kumpe.hadooptimizer.examples.painting.SvgRenderer.output";
075    
076            /**
077             * The Mapper to do the actual work. See {@link SvgRenderer} for details. *
078             * 
079             * @author <a href="http://kumpe.de/christian/java">Christian Kumpe</a>
080             */
081            public static class SvgRendererMapper extends
082                            Mapper<LongWritable, Text, LongWritable, ObjectWritable> {
083                    private Element[] triangles;
084                    private DOMSource source;
085                    private Transformer transformer;
086                    private StreamResult result;
087                    private String outputPath;
088    
089                    @Override
090                    protected void setup(final Context context) throws IOException,
091                                    InterruptedException {
092                            try {
093                                    // parse the result-template
094                                    final DocumentBuilder documentBuilder = DocumentBuilderFactory
095                                                    .newInstance().newDocumentBuilder();
096                                    final Document document = documentBuilder.parse(getClass()
097                                                    .getResourceAsStream("/ResultTemplate.svg"));
098    
099                                    // extract necessary elements
100                                    final XPath xPath = XPathFactory.newInstance().newXPath();
101                                    final Element triangle = (Element) xPath.evaluate(
102                                                    "//path[@id='dreieck']", document, XPathConstants.NODE);
103                                    triangle.removeAttribute("id");
104                                    final Element parent = (Element) triangle.getParentNode();
105                                    parent.removeChild(triangle);
106    
107                                    // create 50 triangles
108                                    triangles = new Element[50];
109                                    for (int i = 0; i < triangles.length; i++) {
110                                            triangles[i] = (Element) triangle.cloneNode(false);
111                                            parent.appendChild(triangles[i]);
112                                    }
113    
114                                    // initialize the transformer for writing the SVG-output
115                                    source = new DOMSource(document);
116                                    result = new StreamResult();
117                                    transformer = TransformerFactory.newInstance().newTransformer();
118    
119                                    outputPath = context.getConfiguration().get(OUTPUT_PATH);
120                            } catch (final DOMException e) {
121                                    throw new RuntimeException(e);
122                            } catch (final ParserConfigurationException e) {
123                                    throw new RuntimeException(e);
124                            } catch (final SAXException e) {
125                                    throw new RuntimeException(e);
126                            } catch (final XPathExpressionException e) {
127                                    throw new RuntimeException(e);
128                            } catch (final TransformerConfigurationException e) {
129                                    throw new RuntimeException(e);
130                            } catch (final TransformerFactoryConfigurationError e) {
131                                    throw new RuntimeException(e);
132                            }
133                    }
134    
135                    /**
136                     * Parses the given line and if the best result of every 1000th cycle,
137                     * create an SVG-image and write the parsed double-values into the
138                     * output.
139                     */
140                    @Override
141                    protected void map(final LongWritable key, final Text value,
142                                    final Context context) throws IOException, InterruptedException {
143                            final String line = value.toString();
144                            if (line.startsWith("#")) {
145                                    // it's a comment
146                                    return;
147                            }
148    
149                            final String[] parts = line.split("[ \\t\\[\\],]+");
150                            if (!"0".equals(parts[1])) {
151                                    // it's not the best result of the cycle
152                                    return;
153                            }
154    
155                            final long cycle = Long.parseLong(parts[0]);
156                            if (cycle % 1000 != 0) {
157                                    // it's not thousand cycle
158                                    return;
159                            }
160    
161                            final double[] values = new double[parts.length];
162                            for (int i = 0; i < parts.length; i++) {
163                                    if (4 == i) {
164                                            // remove "increment="-prefix
165                                            parts[i] = parts[i].split("=")[1];
166                                    }
167                                    values[i] = parseDouble(parts[i]);
168                            }
169    
170                            configureTriangles(values);
171                            writeSvgResultDocument(cycle);
172    
173                            context.write(new LongWritable(cycle), new ObjectWritable(values));
174                    }
175    
176                    private void configureTriangles(final double[] values) {
177                            for (int i = 0; i < triangles.length; i++) {
178                                    // extract and clip the values
179                                    final double opacity = clip(values[7 * i + 5 + 0] / 312, 0, 1);
180                                    final double x1 = clip(values[7 * i + 5 + 1], 0, 312);
181                                    final double y1 = clip(values[7 * i + 5 + 2], 0, 312);
182                                    final double x2 = clip(values[7 * i + 5 + 3], 0, 312);
183                                    final double y2 = clip(values[7 * i + 5 + 4], 0, 312);
184                                    final double x3 = clip(values[7 * i + 5 + 5], 0, 312);
185                                    final double y3 = clip(values[7 * i + 5 + 6], 0, 312);
186    
187                                    // set the coordinates
188                                    triangles[i].setAttribute(
189                                                    "d",
190                                                    format(Locale.US, "M %f,%f %f,%f %f,%f %1$f,%2$f z",
191                                                                    x1, y1, x2, y2, x3, y3));
192    
193                                    // set the opacity
194                                    triangles[i]
195                                                    .setAttribute(
196                                                                    "style",
197                                                                    format(Locale.US,
198                                                                                    "fill:#000000;stroke:#00ffff;stroke-width:0.1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.75;fill-opacity:%f",
199                                                                                    opacity));
200                            }
201                    }
202    
203                    private void writeSvgResultDocument(final long cycle)
204                                    throws IOException {
205                            // create the SVG-file
206                            final Path outputPath = new Path(format("%s/result%06d.svg",
207                                            this.outputPath, cycle));
208                            final FileSystem fileSystem = outputPath
209                                            .getFileSystem(new Configuration());
210                            final OutputStream outputStream = fileSystem.create(outputPath);
211                            result.setOutputStream(outputStream);
212    
213                            try {
214                                    // write the SVG-file
215                                    transformer.transform(source, result);
216                            } catch (final TransformerException e) {
217                                    throw new RuntimeException(e);
218                            }
219    
220                            outputStream.close();
221                    }
222    
223                    private double clip(final double value, final double min,
224                                    final double max) {
225                            final double diff = max - min;
226                            return Math.abs(Math.abs(value % (diff * 2)) - diff) + min;
227                    }
228            }
229    
230            @Override
231            public Options createOptions() {
232                    // no options needed
233                    return new Options();
234            }
235    
236            @Override
237            public void execute() throws Exception {
238                    final String inputPath = (String) commandLine.getArgList().get(0);
239                    final Configuration conf = getConf();
240                    conf.set(OUTPUT_PATH, baseDir);
241    
242                    final Job job = new Job(conf, getClass().getSimpleName());
243                    job.setJarByClass(IndividualInputFormat.class);
244                    job.setMapperClass(SvgRendererMapper.class);
245                    // use the identity-reducer
246                    job.setReducerClass(Reducer.class);
247                    job.setNumReduceTasks(1);
248    
249                    job.setInputFormatClass(TextInputFormat.class);
250                    TextInputFormat.setMinInputSplitSize(job, 256 * 1024 * 1024);
251                    TextInputFormat.setInputPaths(job, new Path(inputPath));
252    
253                    job.setOutputKeyClass(LongWritable.class);
254                    job.setOutputValueClass(ObjectWritable.class);
255                    job.setOutputFormatClass(SequenceFileOutputFormat.class);
256                    SequenceFileOutputFormat.setOutputPath(job, new Path(baseDir
257                                    + "/output"));
258    
259                    if (!job.waitForCompletion(true)) {
260                            throw new RuntimeException("Job failed.");
261                    }
262            }
263    
264            @Override
265            protected String getVersionInfo() {
266                    return "$Id: SvgRenderer.java 3909 2011-04-25 10:01:48Z baumbart $";
267            }
268    }