/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.configure.adapter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jnode.configure.Configure;
import org.jnode.configure.ConfigureException;
import org.jnode.configure.PropertySet;
import org.jnode.configure.ScriptParser;
import org.jnode.configure.PropertySet.Property;
/**
* This is a base class for File adapters that uses {@link java.util.Properties}
* objects as an intermediate representation.
*
* @author crawley@jnode.org
*/
public abstract class BasePropertyFileAdapter implements FileAdapter {
interface ValueCodec {
public String getValidModifiers();
public String encodeProperty(String propName, String propValue, String modifiers)
throws ConfigureException;
}
private final ValueCodec codec;
private final boolean loadSupported;
private final boolean saveSupported;
private static final Pattern AT_AT_CONTENTS_PATTERN =
Pattern.compile("(" + ScriptParser.NAME_PATTERN.pattern() + ")/?(\\W*)");
protected abstract void loadFromFile(Properties props, InputStream imput) throws IOException;
protected abstract void saveToFile(Properties props, OutputStream output, String comment)
throws IOException;
public BasePropertyFileAdapter(ValueCodec codec, boolean loadSupported, boolean saveSupported) {
super();
this.codec = codec;
this.loadSupported = loadSupported;
this.saveSupported = saveSupported;
}
/**
* Return <code>true</code> if this adapter supports loading of
* properties.
*/
public boolean isLoadSupported() {
return loadSupported;
}
/**
* Return <code>true</code> if this adapter supports saving of properties.
* (If not, the file format requires the use of a template file.)
*/
public boolean isSaveSupported() {
return saveSupported;
}
public final boolean wasSourceGenerated(PropertySet propSet) throws ConfigureException {
File propFile = propSet.getFile();
if (!propFile.exists()) {
return false;
}
String expectedFirstLine;
File templateFile = propSet.getTemplateFile();
if (templateFile != null && templateFile.exists()) {
expectedFirstLine = readFirstLine(templateFile);
} else {
expectedFirstLine = getSignatureLine();
}
String firstLine = readFirstLine(propFile);
return (firstLine == null || expectedFirstLine == null ||
firstLine.equals(expectedFirstLine.trim()));
}
/**
* Return the first non-trivial line in the file; i.e. the first line of the
* file whose length is > 3 after trimming. (A trimmed line with 1 to 3
* characters is most likely to be some kind of comment marker.)
*
* @param file the file to be read.
* @return the line or <code>null</code>.
*/
private String readFirstLine(File file) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(file));
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.length() > 3) {
return line;
}
}
return null;
} catch (IOException ex) {
return null;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException ex) {
// ignore
}
}
}
}
/**
* Override this method to return a fixed 'signature' line that a generated
* file will started.
*
* @return the signature line or <code>null</code>.
*/
protected String getSignatureLine() {
return null;
}
public void load(PropertySet propSet, Configure configure) throws ConfigureException {
File file = propSet.getFile();
File defaultFile = propSet.getDefaultFile();
Properties properties = new Properties();
if (loadSupported) {
InputStream in = null;
try {
if (!file.exists() && defaultFile != null) {
if (defaultFile.exists()) {
configure.debug("Taking initial values for the '" + file +
"' properties from '" + defaultFile + "'.");
file = defaultFile;
}
}
in = new BufferedInputStream(new FileInputStream(file));
loadFromFile(properties, in);
} catch (FileNotFoundException ex) {
// Fall back to the builtin default property values
configure.debug("Taking initial values for the '" + file +
"' properties from the builtin defaults.");
} catch (IOException ex) {
throw new ConfigureException("Problem loading properties from '" + file + "'.", ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
// ignore
}
}
}
}
for (Object key : properties.keySet()) {
String value = (String) properties.get(key);
Property prop = propSet.getProperty((String) key);
if (prop != null) {
PropertySet.Value defaultValue = prop.getType().fromValue(value);
configure.debug("Setting default value for " + key + " to " +
(defaultValue == null ? "null" : defaultValue.toString()));
prop.setDefaultValue(defaultValue);
}
}
}
public void save(PropertySet propSet, Configure configure) throws ConfigureException {
// Harvest the properties to be written into a temporary Properties Object
Properties properties = new Properties();
for (Map.Entry<String, Property> entry : propSet.getProperties().entrySet()) {
PropertySet.Value propValue = entry.getValue().getValue();
if (propValue == null) {
configure.debug("Using default for unset property " + entry.getKey());
propValue = entry.getValue().getDefaultValue();
}
String text = (propValue == null) ? "" : propValue.getText();
configure.debug("Property " + entry.getKey() + " is \"" + text + "\"");
properties.setProperty(entry.getKey(), text);
}
// Output properties, either using the child class'es saveToFile method
// or by using the template expansion mechanism.
OutputStream os = null;
InputStream is = null;
File toFile = propSet.getFile();
File templateFile = propSet.getTemplateFile();
try {
os = new FileOutputStream(toFile);
if (templateFile == null && saveSupported) {
saveToFile(properties, new BufferedOutputStream(os),
"Expanded by JNode 'configure' tool");
} else {
try {
is = new FileInputStream(templateFile);
} catch (FileNotFoundException ex) {
throw new ConfigureException("Cannot read template file", ex);
}
expandToTemplate(properties, is, os, propSet.getMarker(), templateFile);
}
} catch (IOException ex) {
throw new ConfigureException("Cannot save properties to '" + toFile + "'.", ex);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException ex) {
// ignore
}
}
if (is != null) {
try {
is.close();
} catch (IOException ex) {
// ignore
}
}
}
}
/**
* Expand a '@...@' sequences in an input stream, writing the result to an
* output stream. A sequence '@@' turns into a single '@'. A sequence
* '@name@' expands to the value of the named property if it is defined in
* the property set, or the sequence '@name@' if it does not. A CR, NL or
* EOF in an
*
* @...@ sequence is an error.
*
* @param props the properties to be expanded
* @param is the source for the template
* @param os the sink for the expanded template
* @param marker the sequence marker character(defaults to '@')
* @param fileName the template filename for diagnostics
* @throws IOException
* @throws ConfigureException
*/
private void expandToTemplate(
Properties props, InputStream is, OutputStream os, char marker, File file)
throws IOException, ConfigureException {
int ch;
int lineNo = 1;
BufferedReader r = new BufferedReader(new InputStreamReader(is));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(os));
try {
while ((ch = r.read()) != -1) {
if (ch == marker) {
StringBuffer sb = new StringBuffer(20);
while ((ch = r.read()) != marker) {
switch (ch) {
case -1:
throw new ConfigureException("Encountered EOF in a " + marker +
"..." + marker + " sequence: at " + file +
" line " + lineNo);
case '\r':
case '\n':
throw new ConfigureException("Encountered end-of-line in a " +
marker + "..." + marker + " sequence: at " + file +
" line " + lineNo);
default:
sb.append((char) ch);
}
}
if (sb.length() == 0) {
w.write(marker);
} else {
Matcher matcher = AT_AT_CONTENTS_PATTERN.matcher(sb);
if (!matcher.matches()) {
throw new ConfigureException("Malformed @...@ sequence: at " + file +
" line " + lineNo);
}
String name = matcher.group(1);
String modifiers = matcher.group(2);
modifiers = (modifiers == null) ? "" : modifiers;
String propValue = props.getProperty(name);
w.write(codec.encodeProperty(name, propValue, modifiers));
}
} else {
// FIXME ... make this aware of the host OS newline
// sequence.
if (ch == '\n') {
lineNo++;
}
w.write((char) ch);
}
}
} finally {
w.flush();
}
}
}