package com.mlongbo.jfinal.version; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.util.*; /** * @author malongbo */ public class VersionProperty { private static final Logger Log = LoggerFactory.getLogger(VersionProperty.class); private static long lastModified = 0L; private static final Object lock = new Object(); private File file; private Document document; private Map<String,Version> nowVersion = new HashMap<String, Version>(); /** * Creates a new XMLPropertiesTest object. * * @param fileName the full path the file that properties should be read from * and written to. * @throws java.io.IOException if an error occurs loading the properties. */ public VersionProperty(String fileName) throws IOException { this(new File(fileName)); } /* *//** * Loads XML properties from a stream. * * @param in the input stream of XML. * @throws java.io.IOException if an exception occurs when reading the stream. *//* public VersionProperty(InputStream in) throws IOException { if (in != null) { Reader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); buildDoc(reader); } }*/ /** * Creates a new XMLPropertiesTest object. * * @param file the file that properties should be read from and written to. * @throws java.io.IOException if an error occurs loading the properties. */ public VersionProperty(File file) throws IOException { this.file = file; if (!file.exists()) { // Attempt to recover from this error case by seeing if the // tmp file exists. It's possible that the rename of the // tmp file failed the last time Jive was running, // but that it exists now. File tempFile; tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); if (tempFile.exists()) { Log.error("WARNING: " + file.getName() + " was not found, but temp file from " + "previous write operation was. Attempting automatic recovery." + " Please check file for data consistency."); tempFile.renameTo(file); } // There isn't a possible way to recover from the file not // being there, so throw an error. else { throw new FileNotFoundException("XML properties file does not exist: " + file.getName()); } } // Check read and write privs. if (!file.canRead()) { throw new IOException("XML properties file must be readable: " + file.getName()); } if (!file.canWrite()) { throw new IOException("XML properties file must be writable: " + file.getName()); } FileReader reader = new FileReader(file); lastModified = file.lastModified(); buildDoc(reader); } /** * Builds the document XML model up based the given reader of XML data. * @param in the input stream used to build the xml document * @throws java.io.IOException thrown when an error occurs reading the input stream. */ private void buildDoc(Reader in) throws IOException { try { SAXReader xmlReader = new SAXReader(); xmlReader.setEncoding("UTF-8"); document = xmlReader.read(in); buildNowVersion(); } catch (Exception e) { Log.error("Error reading XML properties", e); throw new IOException(e.getMessage()); } finally { if (in != null) { in.close(); } } } private void reCheck() { if (lastModified < file.lastModified()) { synchronized (lock) { if (lastModified < file.lastModified()) { lastModified = file.lastModified(); try { buildDoc(new FileReader(file)); } catch (IOException e) { e.printStackTrace(); } } } } } private void buildNowVersion() { nowVersion.put(ClientType.ANDROID.getType(),loadNowVersion(ClientType.ANDROID)); nowVersion.put(ClientType.IPHONE.getType(),loadNowVersion(ClientType.IPHONE)); } private Version loadNowVersion(ClientType type) { Element clientElement = null; List<Version> versions = null; String nowVersion = ""; switch (type) { case ANDROID: clientElement = getElement("client.android"); break; case IPHONE: clientElement = getElement("client.iphone"); break; default: return null; } nowVersion = clientElement.attributeValue("default"); versions = new ArrayList<Version>(); List elements = clientElement.elements(); if (elements == null) { return null; } Version versionTmp = null; for (Object ele : elements) { Element versionEle = (Element) ele; String version = versionEle.elementText("version"); if (version == null || !nowVersion.equalsIgnoreCase(version)) { continue; } String url = versionEle.elementText("url"); String message = versionEle.elementText("message"); versionTmp = new Version(); versionTmp.setMessage(message); versionTmp.setUrl(url); versionTmp.setVersion(version); return versionTmp; } return null; } /** * Returns an array representation of the given Jive property. Jive * properties are always in the format "prop.name.is.this" which would be * represented as an array of four Strings. * * @param name the name of the Jive property. * @return an array representation of the given Jive property. */ private String[] parsePropertyName(String name) { List<String> propName = new ArrayList<String>(5); // Use a StringTokenizer to tokenize the property name. StringTokenizer tokenizer = new StringTokenizer(name, "."); while (tokenizer.hasMoreTokens()) { propName.add(tokenizer.nextToken()); } return propName.toArray(new String[propName.size()]); } public void addVersion(Version version) { Element clientElement = null; ClientType type = version.getType(); switch (type) { case ANDROID: clientElement = getElement("client.android"); break; case IPHONE: clientElement = getElement("client.iphone"); break; default: return; } Element entry = clientElement.addElement("entry"); Element element; element = entry.addElement("version"); element.setText(version.getVersion()); element = entry.addElement("url"); element.setText(version.getUrl()); element = entry.addElement("message"); element.setText(version.getMessage()); saveProperties(); } public void deleteVersion(String version, ClientType type) { Element clientElement = null; switch (type) { case ANDROID: clientElement = getElement("client.android"); break; case IPHONE: clientElement = getElement("client.iphone"); break; default: return; } List elements = clientElement.elements(); if (elements == null) { return; } Element element = null; for (Object ele : elements) { Element versionEle = (Element) ele; String versionTmp = versionEle.elementText("version"); if (version.equalsIgnoreCase(versionTmp)) { element = versionEle; break; } } if (element != null) { elements.remove(element); saveProperties(); } } public void setVersion(ClientType type, String version) { Element clientElement = null; switch (type) { case ANDROID: clientElement = getElement("client.android"); break; case IPHONE: clientElement = getElement("client.iphone"); break; default: return; } Attribute attribute = clientElement.attribute("default"); if (attribute != null) { attribute.setValue(version); saveProperties(); } } public Version getNowVersion(ClientType type) { reCheck(); return nowVersion.get(type.getType()); } public List<Version> getVersions(ClientType type) { Element clientElement = null; List<Version> versions = null; switch (type) { case ANDROID: clientElement = getElement("client.android"); break; case IPHONE: clientElement = getElement("client.iphone"); break; default: return null; } versions = new ArrayList<Version>(); List elements = clientElement.elements(); if (elements == null) { return versions; } Version versionTmp = null; for (Object ele : elements) { Element versionEle = (Element) ele; String version = versionEle.elementText("version"); String url = versionEle.elementText("url"); String message = versionEle.elementText("message"); versionTmp = new Version(); versionTmp.setMessage(message); versionTmp.setUrl(url); versionTmp.setVersion(version); versions.add(versionTmp); } return versions; } private Element getElement(String name) { String[] propName = parsePropertyName(name); // Search for this property by traversing down the XML heirarchy. Element element = document.getRootElement(); for (int i = 0; i < propName.length; i++) { element = element.element(propName[i]); // Can't find the property so return. if (element == null) { break; } } return element; } /** * Saves the properties to disk as an XML document. A temporary file is * used during the writing process for maximum safety. */ private synchronized void saveProperties() { boolean error = false; // Write data out to a temporary file first. File tempFile = null; Writer writer = null; try { tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tempFile), "UTF-8")); OutputFormat prettyPrinter = OutputFormat.createPrettyPrint(); XMLWriter xmlWriter = new XMLWriter(writer, prettyPrinter); xmlWriter.write(document); } catch (Exception e) { Log.error(e.getMessage(), e); // There were errors so abort replacing the old property file. error = true; } finally { if (writer != null) { try { writer.close(); } catch (IOException e1) { Log.error(e1.getMessage(), e1); error = true; } } } // No errors occured, so delete the main file. if (!error) { // Delete the old file so we can replace it. if (!file.delete()) { Log.error("Error deleting property file: " + file.getAbsolutePath()); return; } // Copy new contents to the file. try { copy(tempFile, file); } catch (Exception e) { Log.error(e.getMessage(), e); // There were errors so abort replacing the old property file. error = true; } // If no errors, delete the temp file. if (!error) { tempFile.delete(); } } } /** * Copies the inFile to the outFile. * * @param inFile The file to copy from * @param outFile The file to copy to * @throws java.io.IOException If there was a problem making the copy */ private static void copy(File inFile, File outFile) throws IOException { FileInputStream fin = null; FileOutputStream fout = null; try { fin = new FileInputStream(inFile); fout = new FileOutputStream(outFile); copy(fin, fout); } finally { try { if (fin != null) fin.close(); } catch (IOException e) { // do nothing } try { if (fout != null) fout.close(); } catch (IOException e) { // do nothing } } } /** * Copies data from an input stream to an output stream * * @param in the stream to copy data from. * @param out the stream to copy data to. * @throws java.io.IOException if there's trouble during the copy. */ private static void copy(InputStream in, OutputStream out) throws IOException { // Do not allow other threads to intrude on streams during copy. synchronized (in) { synchronized (out) { byte[] buffer = new byte[256]; while (true) { int bytesRead = in.read(buffer); if (bytesRead == -1) break; out.write(buffer, 0, bytesRead); } } } } public static void main(String[] args) { try { VersionProperty property = new VersionProperty("version.xml"); List<Version> androidVersions = property.getVersions(ClientType.ANDROID); System.out.println(androidVersions.size()); Version nowVersion = property.getNowVersion(ClientType.ANDROID); System.out.println(nowVersion.getVersion()); Version add = new Version(); add.setVersion("1.1.1.1"); add.setUrl("www.baidu.com"); add.setMessage("baidu"); add.setType(ClientType.ANDROID); property.addVersion(add); property.deleteVersion("1.1.1.1", ClientType.ANDROID); property.setVersion(ClientType.ANDROID,"0.1.0"); } catch (IOException e) { e.printStackTrace(); } } }