Java tutorial
/* * (c) 2005 David B. Bracewell * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.davidbracewell.io.resource; import com.davidbracewell.SystemInfo; import com.davidbracewell.conversion.Cast; import com.davidbracewell.io.EspressoReader; import com.davidbracewell.io.QuietIO; import com.davidbracewell.io.serialization.JavaSerializer; import com.davidbracewell.logging.Logger; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.io.LineProcessor; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; import java.util.*; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * <p> Information about a resource, which abstracts away the specific details on working with the resource. Gives the * ability to open an <code>InputStream</code> and <code>OutputStream</code> as well as manipulate the resource. * Manipulation is implementation specific. </p> * * @author David Bracewell */ public abstract class Resource implements Iterable<String>, Serializable { private static final Pattern ALL_FILE_PATTERN = Pattern.compile(".*"); private static final Logger log = Logger.getLogger(Resource.class); private static final long serialVersionUID = -6238127933568486949L; private Charset charset = null; private boolean isCompressed; /** * <p>Gets the charset for reading and writing. The charset if not specified will be automatically determined during * read.</p> * * @return The charset used for writing and default when reading */ public final Charset getCharset() { return charset; } /** * <p>Sets the charset for reading and writing.</p> * * @param charset The charset to use */ public final void setCharset(Charset charset) { this.charset = charset; } private Charset determineCharset() { if (charset == null) { charset = Charsets.UTF_8; } return charset; } /** * <p> Appends content to this resource. </p> * * @param content The content to append * @throws java.io.IOException Something went wrong appending the content. */ public void append(String content) throws IOException { append(Preconditions.checkNotNull(content).getBytes(determineCharset())); } /** * <p> Appends content to this resource. </p> * * @param byteArray The content to append * @throws java.io.IOException Something went wrong appending the content. */ public void append(byte[] byteArray) throws IOException { throw new UnsupportedEncodingException(); } /** * Opens a writer for writing * * @return A writer * @throws java.io.IOException Something went wrong opening the writer */ public Writer openWriter() throws IOException { return new OutputStreamWriter(openOutputStream(), determineCharset()); } /** * Gets the resource as a <code>File</code>. * * @return A <code>File</code> representing the resource. */ public File asFile() { return null; } /** * Gets the resource as a <code>URL</code>. * * @return A <code>URL</code> representing the resource. * @throws java.net.MalformedURLException the malformed uRL exception */ public URL asURL() throws MalformedURLException { return null; } /** * Gets path in the same mannar as {@link File#path} * * @return The full path to the resource including name */ public String path() { return null; } /** * Gets the name (file name or directory name) of this resource. * * @return The name of the file or directory */ public String baseName() { return null; } /** * @return The string representation of the resource with protocol */ public abstract String resourceDescriptor(); /** * Is directory. * * @return True if the resource is a directory */ public boolean isDirectory() { return false; } /** * Can write. * * @return True if can write to the resource */ public boolean canWrite() { return false; } /** * Can read. * * @return True if can read from the resource */ public boolean canRead() { return false; } /** * <p> Creates a new Resource that is relative to this resource. </p> * * @param relativePath The relative path for the new resource. * @return A new resource that is relative to this resource. */ public Resource getChild(String relativePath) { throw new UnsupportedOperationException(); } /** * Exists boolean. * * @return True if the resource exists, False if the resource does not exist. */ public abstract boolean exists(); /** * <p> Lists all the resources that are directly under this resource. </p> * * @return A list of all the resources one level under this resource. */ public final List<Resource> getChildren() { return getChildren(ALL_FILE_PATTERN, false); } /** * <p> Lists all the resources that are directly under this resource. </p> * * @param recursive Gets all children recursively * @return A list of all the resources one level under this resource. */ public final List<Resource> getChildren(boolean recursive) { return getChildren(ALL_FILE_PATTERN, recursive); } private Pattern convertToRegex(String filePattern) { filePattern = Strings.isNullOrEmpty(filePattern) ? "\\*" : filePattern; filePattern = filePattern.replaceAll("\\.", "\\."); filePattern = filePattern.replaceAll("\\*", ".*"); return Pattern.compile("^" + filePattern + "$"); } /** * Iterator over the children * * @param pattern The pattern to determine what files we want * @param recursive should we iterator recursively? * @return An iterator over the children */ public final Iterator<Resource> childIterator(String pattern, boolean recursive) { Preconditions.checkArgument(Strings.isNullOrEmpty(pattern), "Pattern is invalid."); return new ChildIterator(this, convertToRegex(pattern), recursive); } /** * Iterator over the children * * @param recursive should we iterator recursively? * @return An iterator over the children */ public final Iterator<Resource> childIterator(boolean recursive) { return new ChildIterator(this, ALL_FILE_PATTERN, recursive); } /** * <p> Lists all the resources that are directly under this resource. </p> * * @param pattern The file matching pattern * @return A list of all the resources one level under this resource. */ public final List<Resource> getChildren(String pattern) { return getChildren(convertToRegex(pattern), false); } /** * <p> Lists all the resources that are directly under this resource. </p> * * @param pattern The file matching pattern * @param recursive Gets all children recursively * @return A list of all the resources one level under this resource. */ public final List<Resource> getChildren(String pattern, boolean recursive) { return getChildren(convertToRegex(pattern), recursive); } /** * <p> Lists all the resources that are directly under this resource. </p> * * @param pattern The file matching pattern * @param recursive Gets all children recursively * @return A list of all the resources one level under this resource. */ protected List<Resource> getChildren(Pattern pattern, boolean recursive) { return Collections.emptyList(); } /** * Gets parent. * * @return The parent resource (directory for file, parent directory for a directory) */ public Resource getParent() { return null; } /** * <p> Opens an input stream over this resource. </p> * * @return An input stream over this resource. * @throws java.io.IOException An error happened when obtaining the input stream */ public final InputStream openInputStream() throws IOException { PushbackInputStream is = new PushbackInputStream(createInputStream(), 2); if (isCompressed || isCompressed(is)) { return new GZIPInputStream(is); } return is; } /** * <p> Opens an input stream over this resource. </p> * * @return An input stream over this resource. * @throws java.io.IOException An error happened when obtaining the input stream */ protected InputStream createInputStream() throws IOException { throw new UnsupportedOperationException(); } protected boolean isCompressed(PushbackInputStream pushbackInputStream) throws IOException { if (pushbackInputStream == null) { return false; } byte[] buffer = new byte[2]; int read = pushbackInputStream.read(buffer); boolean isCompressed = false; if (read == 2) { isCompressed = ((buffer[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (buffer[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8))); } pushbackInputStream.unread(buffer); return isCompressed; } /** * Opens a reader using UTF-8 encoding * * @return A reader * @throws java.io.IOException Something went wrong opening the reader */ public Reader openReader() throws IOException { EspressoReader reader = new EspressoReader(openInputStream(), charset); charset = reader.getCharset(); return reader; } /** * <p> Opens an output stream over this resource. </p> * * @return An output stream over this resource. * @throws java.io.IOException An error happened when obtaining the output stream */ public final OutputStream openOutputStream() throws IOException { if (isCompressed) { return new GZIPOutputStream(createOutputStream()); } return createOutputStream(); } /** * <p> Creates an output stream over this resource. </p> * * @return An output stream over this resource. * @throws java.io.IOException An error happened when obtaining the output stream */ protected OutputStream createOutputStream() throws IOException { throw new UnsupportedOperationException(); } /** * <p> Reads the resource into an array of bytes. </p> * * @return An array of bytes representing the content of the resource. * @throws java.io.IOException Something went wrong reading the resource. */ public final byte[] readBytes() throws IOException { try (ByteArrayOutputStream byteWriter = new ByteArrayOutputStream(); BufferedInputStream byteReader = new BufferedInputStream(openInputStream())) { int bytesRead; byte[] buffer = new byte[1024]; while ((bytesRead = byteReader.read(buffer)) != -1) { byteWriter.write(buffer, 0, bytesRead); } return byteWriter.toByteArray(); } } /** * <p> Reads in the resource as a String using UTF-8. </p> * * @return A string representing the contents of the file. * @throws java.io.IOException An error happened when reading the file. */ public final String readToString() throws IOException { return read(new ReadLineToString()); } /** * Reads lines in the resource to a list of string * * @return A list of string representing the contents of the file. * @throws java.io.IOException An error happened when reading the file. */ public final List<String> readLines() throws IOException { return read(new ReadLineToStringList()); } /** * <p> Reads in the resource line by line passing each line through the given procedure. </p> * * @param processor A <code>LineProcessor</code> to apply to each line in the resource. * @return the t * @throws java.io.IOException An error happened when reading the file. */ public final <T> T read(LineProcessor<T> processor) throws IOException { if (!exists()) { throw new IOException(resourceDescriptor() + " does not exist!"); } try (BufferedReader bufferedReader = new BufferedReader(openReader())) { String line; while ((line = bufferedReader.readLine()) != null) { if (!processor.processLine(line)) { break; } } } return processor.getResult(); } /** * Deletes the resource * * @return true if the deletion was successful */ public final boolean delete() { return delete(false); } /** * Deletes the resource * * @param recursively true if should recursively delete everything under this resource * @return true if the deletion was successful */ public boolean delete(boolean recursively) { return false; } /** * Deletes the resource on ext */ public void deleteOnExit() { } /** * Serializes an object to the resource * * @param object The object to serialize * @throws java.io.IOException An error happened writing the object */ public final void writeObject(Object object) throws Exception { new JavaSerializer().serialize(object, this); } /** * Deserializes an object from a resource * * @return the t * @throws java.io.IOException An error happened reading the object */ public final <T> T readObject() throws Exception { return Cast.as(new JavaSerializer().deserialize(this, Object.class)); } /** * <p> Writes a byte array to the resource. </p> * * @param content The content to write. * @throws java.io.IOException Something went wrong writing to the file */ public final void write(byte[] content) throws IOException { Preconditions.checkNotNull(content); try (OutputStream os = openOutputStream()) { os.write(content); } } /** * <p> Writes a string to the resource using UTF-8. </p> * * @param content The content to write. * @throws java.io.IOException Something went wrong writing to the file */ public final void write(String content) throws IOException { write(Preconditions.checkNotNull(content).getBytes(determineCharset())); } /** * Mkdirs boolean. * * @return the boolean * @see java.io.File#mkdirs() */ public boolean mkdirs() { return false; } /** * Mkdir boolean. * * @return the boolean * @see java.io.File#mkdir() */ public boolean mkdir() { return false; } @Override public Iterator<String> iterator() { return new ResourceIterator(); } /** * Is compressed. * * @return True if the resources is gzipped compressed */ public boolean isCompressed() { return isCompressed; } /** * Sets is compressed. * * @param isCompressed True if the resources is gzipped compressed */ public final void setIsCompressed(boolean isCompressed) { this.isCompressed = isCompressed; } @Override public String toString() { return resourceDescriptor(); } private static class ChildIterator implements Iterator<Resource> { private final Pattern filePattern; private final Queue<Resource> queue = Lists.newLinkedList(); private final boolean recursive; /** * Instantiates a new Child iterator. * * @param startingPoint the starting point * @param filePattern the file pattern * @param recursive the recursive */ public ChildIterator(Resource startingPoint, Pattern filePattern, boolean recursive) { this.filePattern = filePattern; queue.addAll(startingPoint.getChildren(filePattern, false)); this.recursive = recursive; advance(); } private void advance() { if (queue.isEmpty()) { return; } if (queue.peek().isDirectory()) { if (recursive) { queue.addAll(queue.peek().getChildren(filePattern, false)); } } } @Override public boolean hasNext() { return !queue.isEmpty(); } @Override public Resource next() { if (queue.isEmpty()) { throw new NoSuchElementException(); } Resource next = queue.remove(); advance(); return next; } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * A LineProcessor that reads every line into a String */ protected static class ReadLineToString implements LineProcessor<String> { private final StringBuilder sb = new StringBuilder(); public boolean processLine(String element) { sb.append(element).append(SystemInfo.LINE_SEPARATOR); return true; } public String getResult() { return sb.toString(); } }//END OF Resource$ReadLineToString /** * A LineProcessor that reads every line into a List of String */ protected static class ReadLineToStringList implements LineProcessor<List<String>> { private final List<String> list = new ArrayList<>(); public List<String> getResult() { return list; } public boolean processLine(String element) { list.add(element); return true; } }//END OF Resource$ReadLineToStringList private class ResourceIterator implements Iterator<String> { String line; BufferedReader reader; /** * Instantiates a new Resource iterator. */ public ResourceIterator() { this.line = null; try { this.reader = new BufferedReader(openReader()); } catch (IOException e) { log.severe(e); QuietIO.closeQuietly(reader); } readLine(); } void readLine() { try { line = reader.readLine(); } catch (IOException e) { log.severe(e); line = null; } if (line == null) { QuietIO.closeQuietly(reader); } } @Override public boolean hasNext() { return line != null; } @Override public String next() { String next = line; readLine(); return next; } @Override public void remove() { throw new UnsupportedOperationException(); } } }// END OF Resource