/******************************************************************************* * Copyright (c) 2012 VMWare, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VMWare, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.runtime.shared; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.LinkedHashSet; import java.util.Set; /** * The purpose of this class is to segregate all the code relating to the "DependencyFileFormat". * The "DependencyFile" is a file that is written out by some class injected into an external * grails process and later read by STS to initialise the classpath container. * <p> * This class provides methods for reading and writing the file. This class is classloaded from * both the external Grails process and STS, as such, to be safe, it should be depending only * on some low-level Java libraries that are readily available in any Java environment and * pose little risk of being compromised by modifications to the Grails execution environment * that are beyond our control (e.g. see STS-1530 where installing a Grails plugin can cause * our use of the JAXP library to write the dependency file to fail). */ public class DependencyFileFormat { /** * Everybody reading / writing the dependency file should use the same encoding. * don't leave it up to the environment to decide. */ private static final String ENCODING = "UTF-8"; //////////////// Writing ////////////////////////////////////////////////////////////////////////////// private static class DepWriter { private OutputStream out; public DepWriter(File file) throws FileNotFoundException { out = new BufferedOutputStream(new FileOutputStream(file)); } public void write(String header, Set<String> entries) throws IOException { println(header); println(""+entries.size()); for (String entry : entries) { println(entry); } } private void write(String header, String entry) throws IOException { println(header); println(entry); } public void write(String header, int entry) throws IOException { write(header, ""+entry); } public void close() { try { out.close(); } catch (IOException e) { //Ignore... } } /** * Writes a String to a line of the file escaping newline characters. * <p> * We only use '\n' as line terminator, regardless of platform. */ private void println(String line) throws IOException { byte[] bytes = line.getBytes(ENCODING); for (byte b : bytes) { if (b=='\n' || b=='\\') { out.write('\\'); } out.write(b); } out.write('\n'); out.flush(); } } public static void write(File file, DependencyData data) throws IOException { DepWriter w = null; try { w =new DepWriter(file); w.write("#dependencies", data.getDependencies()); w.write("#sources", data.getSources()); w.write("#workDir", data.getWorkDir()); w.write("#plugin descriptors", data.getPluginDescriptors()); w.write("#plugins directory", data.getPluginsDirectory()); w.write("#plugin classes dir", data.getPluginClassesDirectory()); w.write("#port", data.getServerPort()); } finally { if (w!=null) { w.close(); } } } ///////////////////////// Reading ////////////////////////////////////////////////////////////// public static class DepReader { InputStream in; InfiniteByteBuffer buf = new InfiniteByteBuffer(); public DepReader(File file) throws FileNotFoundException { in = new BufferedInputStream(new FileInputStream(file)); } public Set<String> readSet(String expectHeader) throws IOException { readHeader(expectHeader); Set<String> set = new LinkedHashSet<String>(); int size = readInt(); for (int i = 0; i < size; i++) { set.add(readln()); } return set; } private void readHeader(String expectHeader) throws IOException { String header = readln(); if (!expectHeader.equals(header)) { throw new IOException("DependencyFileFormat expected "+expectHeader+" but found "+header); } } private String readln() throws IOException { buf.clear(); while (true) { byte b = readByte(); if (b=='\\') { b = readByte(); } else if (b=='\n') { break; } buf.add(b); } return buf.getString(); } private byte readByte() throws IOException { int b = in.read(); if (b<0) { throw new EOFException(); } return (byte)b; } private int readInt() throws IOException { try { return Integer.parseInt(readln()); } catch (NumberFormatException e) { throw new IOException("DependencyDataFileFormat"); } } public String readString(String expectHeader) throws IOException { readHeader(expectHeader); return readln(); } public int readInteger(String expectHeader) throws IOException { readHeader(expectHeader); String str = readln(); return Integer.parseInt(str); } public void close() { try { in.close(); } catch (IOException e) { //Ignore ... } } } public static DependencyData read(File file) throws IOException { DepReader r = null; try { r = new DepReader(file); Set<String> dependencies = r.readSet("#dependencies"); Set<String> pluginSourceFolders = r.readSet("#sources"); String workDirFile = r.readString("#workDir"); Set<String> pluginXmlFiles = r.readSet("#plugin descriptors"); String pluginsDirectoryFile = r.readString("#plugins directory"); String pluginClassesDir = r.readString("#plugin classes dir"); int serverPort = r.readInteger("#port"); return new DependencyData(pluginSourceFolders, dependencies, workDirFile, pluginsDirectoryFile, pluginXmlFiles, pluginClassesDir, serverPort); } finally { if (r!=null) { r.close(); } } } /////////// Util /////////////////////////////////////////////////////////// /** * Byte buffer that grows in size to accomodate what is put in it. */ public static class InfiniteByteBuffer { private static final int SIZE_INCREMENT = 128; private int i = 0; private byte[] bytes = new byte[SIZE_INCREMENT]; /** * Make buffer empty, so it can be reused. */ public void clear() { i = 0; } public void add(byte b) { if (i>=bytes.length) { grow(); } bytes[i++] = b; } private void grow() { byte[] newBytes = new byte[bytes.length+SIZE_INCREMENT]; System.arraycopy(bytes, 0, newBytes, 0, bytes.length); bytes = newBytes; } /** * Convert current contents of byte buffer to String using ENCODODIN. * @throws UnsupportedEncodingException */ public String getString() { try { return new String(bytes, 0, i, ENCODING); } catch (UnsupportedEncodingException e) { //Unless we put something exotic/bad for ENCODING constant this should never // happen. throw new RuntimeException(e); } } /** * For easier debugging */ @Override public String toString() { return getString(); } } }