/******************************************************************************* * Signavio Core Components * Copyright (C) 2012 Signavio GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package com.signavio.buildapps.sscompress; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class SSCompressor { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { if(args.length < 1) throw new Exception("Missing argument! Usage: java SSCompressor <SSDirectory>"); //get stencil set directory from arguments String ssDirString = args[0]; //get stencil set configuration file File ssConf = new File(ssDirString + "/stencilsets.json"); if(!ssConf.exists()) throw new Exception("File " + ssDirString + "/stencilsets.json does not exist."); //read stencil set configuration StringBuffer jsonObjStr = readFile(ssConf); JSONArray jsonObj = new JSONArray(jsonObjStr.toString()); //iterate all stencil set configurations for(int i = 0; i < jsonObj.length(); i++) { JSONObject ssObj = jsonObj.getJSONObject(i); /* * StencilsSet has to build out of existing stencil set and extension.. */ if (ssObj.has("basestencilset") && ssObj.has("extensionstointegrate")){ if (ssObj.has("uri")) { String ssUri = ssObj.getString("uri"); File ssFile = new File(ssDirString + ssUri); if (ssFile.exists()){ throw new Exception("The stencil set (" + ssDirString + ssUri + ") that should be created does already exist."); } ssFile = createFile(ssDirString, ssUri); File baseSsFile = getStencilSet(ssObj.getString("basestencilset"), ssConf, ssDirString); JSONObject BaseSsJson = new JSONObject( readFile(baseSsFile).toString() ); copyDataFromDirectory(baseSsFile.getParentFile(), ssFile.getParentFile()); JSONArray extensions = ssObj.getJSONArray("extensionstointegrate"); List<JSONObject> extensionsJson = new ArrayList<JSONObject>(); for (int j = 0; j < extensions.length(); j++) { File extensionFile = getStencilSetExtension( extensions.getString(j), new File(ssDirString + File.separator + "extensions" + File.separator + "extensions.json"), ssDirString + File.separator + "extensions" + File.separator ); copyDataFromDirectory(extensionFile.getParentFile(), ssFile.getParentFile()); extensionsJson.add(new JSONObject( readFile(extensionFile).toString() )); } JSONObject jsonSS = createJsonFor(ssObj, BaseSsJson, extensionsJson); writeFile(ssFile, jsonSS.toString() ); } } else if (ssObj.has("extensionstointegrate")){ String ssUri = ssObj.getString("uri"); File ssFile = new File(ssDirString + ssUri); if(!ssFile.exists()) throw new Exception("Stencil set " + ssDirString + ssUri + " that is referenced in stencil set configuration file does not exist."); JSONArray extensions = ssObj.getJSONArray("extensionstointegrate"); JSONObject ssJson = new JSONObject( readFile(ssFile).toString() ); for (int j = 0; j < extensions.length(); j++) { File extensionFile = getStencilSetExtension( extensions.getString(j), new File(ssDirString + File.separator + "extensions" + File.separator + "extensions.json"), ssDirString + File.separator + "extensions" + File.separator ); copyDataFromDirectory(extensionFile.getParentFile(), ssFile.getParentFile()); JSONObject extension = new JSONObject( readFile(extensionFile).toString() ); if (ssJson.getString("namespace").equals(extension.getString("extends"))) { merge(ssJson, extension); } else { System.out.println("[Warning] could not merge " + ssJson.getString("namespace") + " with extension " + extension.getString("namespace")); } } writeFile(ssFile, ssJson.toString()); } //get stencil set location if(ssObj.has("uri")) { String ssUri = ssObj.getString("uri"); File ssFile = new File(ssDirString + ssUri); if(!ssFile.exists()) throw new Exception("Stencil set " + ssDirString + ssUri + " that is referenced in stencil set configuration file does not exist."); String ssDir = ssFile.getParent(); //read stencil set file StringBuffer ssString = readFile(ssFile); // store copy of original stencilset file (w/o SVG includes) with postfix '-nosvg' int pIdx = ssUri.lastIndexOf('.'); File ssNoSvgFile = new File(ssDirString + ssUri.substring(0, pIdx) + "-nosvg" + ssUri.substring(pIdx)); writeFile(ssNoSvgFile, ssString.toString()); //***include svg files*** //get view property Pattern pattern = Pattern.compile("[\"\']view[\"\']\\s*:\\s*[\"\']\\S+?[\"\']"); Matcher matcher = pattern.matcher(ssString); StringBuffer tempSS = new StringBuffer(); int lastIndex = 0; //iterate all view properties while(matcher.find()) { tempSS.append(ssString.substring(lastIndex, matcher.start())); lastIndex = matcher.end(); //get svg file name String filename = matcher.group().replaceFirst("[\"\']view[\"\']\\s*:\\s*[\"\']", ""); filename = filename.substring(0, filename.length()-1); //get svg file File svgFile = new File(ssDir + "/view/" + filename); if(!svgFile.exists()) throw new Exception("SVG File " + svgFile.getPath() + " does not exists!. Compressing stencil sets aborted!"); StringBuffer svgString = readFile(svgFile); //check, if svgString is a valid xml file /*try { DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); DocumentBuilder document = builder.newDocumentBuilder(); document.parse(svgString.toString()); } catch(Exception e) { throw new Exception("File " + svgFile.getCanonicalPath() + " is not a valid XML file: " + e.getMessage()); }*/ //append file content to output json file (replacing existing json file) tempSS.append("\"view\":\""); tempSS.append(svgString.toString().replaceAll("[\\t\\n\\x0B\\f\\r]", " ").replaceAll("\"", "\\\\\"")); //newSS.append(filename); tempSS.append("\""); } tempSS.append(ssString.substring(lastIndex)); //***end include svg files*** /* * BAD IDEA, BECAUSE IT INCREASES THROUGHPUT //***include png files //get icon property pattern = Pattern.compile("[\"\']icon[\"\']\\s*:\\s*[\"\']\\S+[\"\']"); matcher = pattern.matcher(tempSS); StringBuffer finalSS = new StringBuffer(); lastIndex = 0; //iterate all icon properties while(matcher.find()) { finalSS.append(tempSS.substring(lastIndex, matcher.start())); lastIndex = matcher.end(); //get icon file name String filename = matcher.group().replaceFirst("[\"\']icon[\"\']\\s*:\\s*[\"\']", ""); filename = filename.substring(0, filename.length()-1); //get icon file File pngFile = new File(ssDir + "/icons/" + filename); if(!pngFile.exists()) throw new Exception("SVG File " + pngFile.getPath() + " does not exists!. Compressing stencil sets aborted!"); StringBuffer pngString = readFile(pngFile); //append file content to output json file (replacing existing json file) finalSS.append("\"icon\":\"javascript:"); finalSS.append(encodeBase64(pngString.toString())); finalSS.append("\""); } finalSS.append(tempSS.substring(lastIndex)); //***end include png files */ //write compressed stencil set file writeFile(ssFile, tempSS.toString()); System.out.println("Compressed stencil set file " + ssFile.getCanonicalPath()); } } } private static StringBuffer readFile(File file) throws Exception { FileInputStream fin = new FileInputStream(file); StringBuffer result = new StringBuffer(); String thisLine = ""; BufferedReader myInput = new BufferedReader(new InputStreamReader(fin)); while ((thisLine = myInput.readLine()) != null) { result.append(thisLine); result.append("\n"); } myInput.close(); fin.close(); return result; } private static void writeFile(File file, String text) throws Exception { FileOutputStream fos = new FileOutputStream(file); BufferedWriter myOutput = new BufferedWriter(new OutputStreamWriter(fos)); myOutput.write(text); myOutput.flush(); myOutput.close(); fos.close(); } /*private static String encodeBase64(String text) { byte[] encoded = Base64.encodeBase64(text.getBytes()); return new String(encoded); }*/ private static JSONObject createJsonFor(JSONObject newStencilSetData, JSONObject baseStencilSetData, List<JSONObject> extensions) throws JSONException { JSONObject result = new JSONObject(); if (newStencilSetData.has("title")) result.put("title", newStencilSetData.getString("title")); if (newStencilSetData.has("title_de")) result.put("title_de", newStencilSetData.getString("title_de")); if (newStencilSetData.has("namespace")) result.put("namespace", newStencilSetData.getString("namespace")); if (newStencilSetData.has("description")) result.put("description", newStencilSetData.getString("description")); if (newStencilSetData.has("description_de")) result.put("description_de", newStencilSetData.getString("description_de")); if (baseStencilSetData.has("propertyPackages")) result.put("propertyPackages", baseStencilSetData.getJSONArray("propertyPackages") ); if (baseStencilSetData.has("stencils")) result.put("stencils", baseStencilSetData.getJSONArray("stencils") ); if (baseStencilSetData.has("rules")) result.put("rules", baseStencilSetData.getJSONObject("rules") ); for (JSONObject extension : extensions) { if (baseStencilSetData.getString("namespace").equals(extension.getString("extends"))) { merge(result, extension); } else { System.out.println("[Warning] could not merge " + baseStencilSetData.getString("namespace") + " with extension " + extension.getString("namespace")); } } return result; } /** * This method merges the stencil set with an extension according to the * rules used in oryx/editor/client/scripts/Core/StencilSet/stencilset.js. * * This means, that the following steps are executed: * - load new stencils * - load additional properties * - remove stencil properties * - remove stencils * in this specific order! * * As this strict order cannot be used here (due to the missing JSONArray.remove() * method), another order was choosen that produces the same outcome * as the original one (by additional check). */ private static void merge(JSONObject ssObj, JSONObject sseObj) throws JSONException { JSONArray existingStencils = ssObj.getJSONArray("stencils"); JSONArray newStencilArray = new JSONArray(); JSONArray stencilsToBeAdded = getStencilsToBeAdded(sseObj); Map<String, List<JSONObject>> propertiesToBeAdded = getPropertiesToBeAdded(sseObj); Map<String, Set<String>> propertiesToBeRemoved = getPropertiesToBeRemoved(sseObj); Set<String> stencilsToBeRemoved = getStencilsToBeRemoved(sseObj); // Add stencils for (int i = 0; i < stencilsToBeAdded.length(); i++) { JSONObject stencil = stencilsToBeAdded.getJSONObject(i); existingStencils.put(stencil); } // Copy stencils from existing to new list if they should not be removed. // Modify their properties while doing so. if (stencilsToBeRemoved.size() > 0 || propertiesToBeRemoved.size() > 0 || propertiesToBeAdded.size() > 0) { for (int i = 0; i < existingStencils.length(); i++) { JSONObject stencil = existingStencils.getJSONObject(i); String stencilId = stencil.getString("id"); List<String> rolesOfStencil = new ArrayList<String>(); if (stencil.has("roles")) { JSONArray rolesOfStencilArray = stencil.getJSONArray("roles"); for (int j = 0; j < rolesOfStencilArray.length(); j++) { rolesOfStencil.add(rolesOfStencilArray.getString(j)); } } rolesOfStencil.add(stencilId); JSONArray existingProperties; if (stencil.has("properties")) { existingProperties = stencil.getJSONArray("properties"); } else { existingProperties = new JSONArray(); } boolean added = false; // ADD PROPERTIES for (String role : rolesOfStencil) { List<JSONObject> propertiesToBeAddedHere = propertiesToBeAdded.get(role); if (propertiesToBeAddedHere != null) { for (JSONObject property : propertiesToBeAddedHere) { existingProperties.put(property); added = true; } } } // RETAIN PROPERTIES THAT SHOULD NOT BE DELETED Set<String> propertiesToBeRemovedHere = propertiesToBeRemoved.get(stencilId); if (propertiesToBeRemovedHere != null && !propertiesToBeRemovedHere.isEmpty()) { JSONArray newPropertyArray = new JSONArray(); for (int j = 0; j < existingProperties.length(); j++) { JSONObject propertyObj = existingProperties.getJSONObject(j); String propertyId = propertyObj.getString("id"); if (!propertiesToBeRemovedHere.contains( propertyId )){ newPropertyArray.put(propertyObj); } } stencil.put("properties", newPropertyArray); } else if (added){ stencil.put("properties", existingProperties); } // RETAIN STENCILS THAT SHOULD NOT BE DELETED if (!stencilsToBeRemoved.contains( stencilId )){ newStencilArray.put(stencil); } } } ssObj.put("stencils", newStencilArray); } private static JSONArray getStencilsToBeAdded(JSONObject sseObj) throws JSONException { if (sseObj.has("stencils")) { return sseObj.getJSONArray("stencils"); } else { return new JSONArray(); } } private static Map<String, List<JSONObject>> getPropertiesToBeAdded(JSONObject sseObj) throws JSONException { Map<String, List<JSONObject>> result = new HashMap<String, List<JSONObject>>(); if (sseObj.has("properties")){ JSONArray propertiesToBeAdded = sseObj.getJSONArray("properties"); for (int i = 0; i < propertiesToBeAdded.length(); i++) { JSONObject propertyToBeAdded = propertiesToBeAdded.getJSONObject(i); JSONArray roles = propertyToBeAdded.getJSONArray("roles"); JSONArray properties = propertyToBeAdded.getJSONArray("properties"); for (int j = 0; j < roles.length(); j++) { String role = roles.getString(j); List<JSONObject> currentList = result.get(role); if (currentList == null) { currentList = new ArrayList<JSONObject>(); result.put(role, currentList); } for (int k = 0; k < properties.length(); k++) { JSONObject obj = properties.getJSONObject(k); if (!currentList.contains(obj)) { currentList.add(obj); } } } } } return result; } private static Set<String> getStencilsToBeRemoved(JSONObject sseObj) throws JSONException { Set<String> result = new HashSet<String>(); if (sseObj.has("removestencils")){ JSONArray stencilsToBeRemoved = sseObj.getJSONArray("removestencils"); for (int i = 0; i < stencilsToBeRemoved.length(); i++) { result.add(stencilsToBeRemoved.getString(i)); } } return result; } private static Map<String, Set<String>> getPropertiesToBeRemoved(JSONObject sseObj) throws JSONException { Map<String, Set<String>> result = new HashMap<String, Set<String>>(); if (sseObj.has("removeproperties")){ JSONArray propertiesToBeRemoved = sseObj.getJSONArray("removeproperties"); for (int i = 0; i < propertiesToBeRemoved.length(); i++) { JSONObject propertiesOfStencil = propertiesToBeRemoved.getJSONObject(i); String stencilId = propertiesOfStencil.getString("stencil"); JSONArray properties = propertiesOfStencil.getJSONArray("properties"); Set<String> propertySet = new HashSet<String>(); for (int j = 0; j < properties.length(); j++) { propertySet.add(properties.getString(j)); } result.put(stencilId, propertySet); } } return result; } private static void copyDataFromDirectory(File from, File to) throws IOException { for (File src : from.listFiles()) { File target = new File(to.getAbsolutePath() + File.separator + src.getName()); if (src.isFile()) { copyFile(src, target); } else if (src.isDirectory()) { if (!target.exists()) target.mkdir(); copyDataFromDirectory(src, target); } } } public static void copyFile(File in, File out) throws IOException { FileChannel inChannel = new FileInputStream(in).getChannel(); FileChannel outChannel = new FileOutputStream(out).getChannel(); try { inChannel.transferTo(0, inChannel.size(), outChannel); } catch (IOException e) { throw e; } finally { if (inChannel != null) inChannel.close(); if (outChannel != null) outChannel.close(); } } private static File createFile(String existingDir, String newFilePath) throws IOException { int i = newFilePath.lastIndexOf("/"); String head = newFilePath.substring(0, i); File f = new File(existingDir + head); f.mkdirs(); f = new File (existingDir + newFilePath); f.createNewFile(); return f; } private static File getStencilSet(String namespace, File ssConf, String ssDirString) { try { StringBuffer jsonObjStr = readFile(ssConf); JSONArray jsonObj = new JSONArray(jsonObjStr.toString()); for(int i = 0; i < jsonObj.length(); i++) { JSONObject ssObj = jsonObj.getJSONObject(i); if (ssObj.has("namespace") && ssObj.getString("namespace").equals(namespace)) { String path = ssDirString + ssObj.getString("uri"); File ssFile = new File(path.replace(".json", "-nosvg.json")); if (!ssFile.exists()) { ssFile = new File(path); } return ssFile; } } } catch (Exception e) { throw new IllegalArgumentException("Cannot find stencilset with namespace " + namespace + " in " + ssConf.getAbsolutePath() ); } throw new IllegalArgumentException("Cannot find stencilset with namespace " + namespace + " in " + ssConf.getAbsolutePath() ); } private static File getStencilSetExtension(String namespace, File sseConf, String sseDirString) { try { StringBuffer jsonObjStr = readFile(sseConf); JSONObject jsonObj = new JSONObject(jsonObjStr.toString()); JSONArray extensions = jsonObj.getJSONArray("extensions"); for(int i = 0; i < extensions.length(); i++) { JSONObject sseObj = extensions.getJSONObject(i); if (sseObj.has("namespace") && sseObj.getString("namespace").equals(namespace)) { String path = sseDirString + sseObj.getString("definition"); File sseFile = new File(path); return sseFile; } } } catch (Exception e) { e.printStackTrace(); throw new IllegalArgumentException("Cannot find stencilset extension with namespace " + namespace + " in " + sseConf.getAbsolutePath() ); } throw new IllegalArgumentException("Cannot find stencilset extension with namespace " + namespace + " in " + sseConf.getAbsolutePath() ); } }