001: /*
002: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License version
007: * 2 only, as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful, but
010: * WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * General Public License version 2 for more details (a copy is
013: * included at /legal/license.txt).
014: *
015: * You should have received a copy of the GNU General Public License
016: * version 2 along with this work; if not, write to the Free Software
017: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
018: * 02110-1301 USA
019: *
020: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
021: * Clara, CA 95054 or visit www.sun.com if you need additional
022: * information or have any questions.
023: */
024: package com.sun.jump.module.contentstore;
025:
026: import java.io.IOException;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Map;
030: import java.util.Vector;
031:
032: /** Simple in-memory store. */
033: public final class InMemoryStore extends JUMPStore {
034:
035: /** In memory version of content store Node. */
036: private abstract static class Node implements JUMPNode {
037: /** Node uri. */
038: private final String uri;
039: /** Node name. */
040: private final String name;
041: /** <code>true</code> if is a data node. */
042: private final boolean isDataNode;
043:
044: /**
045: * Creats a node.
046: *
047: * @param uri node's uri
048: * @param name node's name
049: * @param isDataNode <code>true</code> if is a data node
050: */
051: protected Node(final String uri, final String name,
052: final boolean isDataNode) {
053: this .uri = uri;
054: this .name = name;
055: this .isDataNode = isDataNode;
056: }
057:
058: /**
059: * Gets node's uri.
060: *
061: * @return node's uri
062: */
063: public String getURI() {
064: return uri;
065: }
066:
067: /**
068: * Gets node's name.
069: *
070: * @return node's name
071: */
072: public String getName() {
073: return name;
074: }
075:
076: /**
077: * Returns <code>true</code> if this node contains data.
078: *
079: * @return <code>true</code> if this node contains data.
080: */
081: public boolean containsData() {
082: return isDataNode;
083: }
084: }
085:
086: /** In memory version of <code>JUMPNode.List</code>. */
087: private static final class ListNode extends Node implements
088: JUMPNode.List {
089: /** Node's children. */
090: private final HashMap children;
091:
092: /**
093: * Creates a list node.
094: *
095: * @param uri node's uri
096: * @param name node's name
097: */
098: private ListNode(final String uri, final String name) {
099: super (uri, name, false);
100: children = new HashMap();
101: }
102:
103: /**
104: * Iterates through node's children.
105: *
106: * @return children iterator
107: */
108: public Iterator getChildren() {
109: return children.values().iterator();
110: }
111:
112: /**
113: * Fetches a node by the name.
114: *
115: * @param name child node name
116: * @return child node if any
117: */
118: private Node get(final String name) {
119: return (Node) children.get(name);
120: }
121:
122: /**
123: * Adds a child node.
124: *
125: * @param name child's name
126: * @param node child node
127: */
128: private void put(final String name, final Node node) {
129: children.put(name, node);
130: }
131:
132: /**
133: * Removes a child node.
134: *
135: * @param name child's name
136: */
137: private void remove(final String name) {
138: children.remove(name);
139: }
140: }
141:
142: /** In memory version of <code>JUMPNode.Data</code>. */
143: private static final class DataNode extends Node implements
144: JUMPNode.Data {
145: /** Node's data. */
146: private final JUMPData data;
147:
148: /**
149: * Creates a data node.
150: *
151: * @param uri node's uri
152: * @param name node's name
153: * @param data node's data
154: */
155: private DataNode(final String uri, final String name,
156: final JUMPData data) {
157: super (uri, name, true);
158: this .data = data;
159: }
160:
161: /**
162: * Fetches node's data.
163: *
164: * @return data
165: */
166: public JUMPData getData() {
167: return data;
168: }
169: }
170:
171: /** Store root. */
172: private final ListNode root = new ListNode("", "");
173:
174: /**
175: * Creates an in memory store.
176: *
177: * @throws IOException if it's impossible to create a store
178: */
179: public InMemoryStore() throws IOException {
180: createNode(".");
181: }
182:
183: /**
184: * Loads this module.
185: *
186: * @param map parameters
187: */
188: public void load(final Map map) {
189: }
190:
191: /**
192: * Unloads this module.
193: */
194: public void unload() {
195: }
196:
197: /**
198: * Creates a data node.
199: *
200: * @param uri node's uri
201: * @param data node's data
202: *
203: * @throws IOException if creation failed
204: */
205: public void createDataNode(final String uri, final JUMPData data)
206: throws IOException {
207: process(uri, new Processor() {
208: public Node process(final ListNode location,
209: final String name) throws IOException {
210: if (location.get(name) != null) { // If a node already exists
211: reportURIProblem(uri, "node already exists");
212: }
213: location.put(name, new DataNode(uri, name, data));
214: return null;
215: }
216: });
217: }
218:
219: /**
220: * Creates a list node.
221: *
222: * @param uri list node uri
223: *
224: * @throws IOException if operation fails
225: */
226: public void createNode(final String uri) throws IOException {
227: final String[] path = splitUri(uri);
228: ListNode location = root;
229: for (int i = 0; i < path.length - 1; i++) {
230: final Node node = location.get(path[i]);
231: if (node == null) {
232: if (i == 0) {
233: throw new IllegalStateException("No root (.)");
234: }
235: // Should create a rest of it manually
236: // Assemble new URI as we go
237: StringBuffer newUri = new StringBuffer(path[0]); // MUST be "."
238: for (int j = 1; j < i; j++) {
239: newUri.append(URI_SEPARATOR);
240: newUri.append(path[j]);
241: }
242: for (int j = i; j < path.length; j++) {
243: final String name = path[j];
244: newUri.append(URI_SEPARATOR);
245: newUri.append(name);
246: ListNode listNode = new ListNode(
247: new String(newUri), name);
248: location.put(name, listNode);
249: location = listNode;
250: }
251: return;
252: }
253:
254: if (!(node instanceof ListNode)) {
255: reportURIProblem(uri, "no such node");
256: }
257: location = (ListNode) node;
258: }
259:
260: final String name = path[path.length - 1];
261: if (location.get(name) != null) {
262: // Node already exists
263: reportURIProblem(uri, "node already exists");
264: }
265: location.put(name, new ListNode(uri, name));
266: }
267:
268: /**
269: * Removes a node.
270: *
271: * @param uri node's uri
272: *
273: * @throws IOException if operation fails
274: */
275: public void deleteNode(final String uri) throws IOException {
276: process(uri, new Processor() {
277: public Node process(final ListNode location,
278: final String name) {
279: location.remove(name);
280: return null;
281: }
282: });
283: }
284:
285: /**
286: * Fetches a node.
287: *
288: * @param uri node's uri
289: *
290: * @return node
291: *
292: * @throws IOException if operation fails
293: */
294: public JUMPNode getNode(final String uri) throws IOException {
295: return process(uri, new Processor() {
296: public Node process(final ListNode location,
297: final String name) {
298: return location.get(name);
299: }
300: });
301: }
302:
303: /**
304: * Updates a node with new data.
305: *
306: * @param uri node's uri
307: * @param data new data
308: *
309: * @throws IOException if operation fails
310: */
311: public void updateDataNode(final String uri, final JUMPData data)
312: throws IOException {
313: process(uri, new Processor() {
314: public Node process(final ListNode location,
315: final String name) throws IOException {
316: // When updating, a node should already exist
317: final Node node = location.get(name);
318: if ((node == null) || !(node instanceof DataNode)) {
319: reportURIProblem(uri, "no such node");
320: }
321: location.put(name, new DataNode(uri, name, data));
322: return null;
323: }
324: });
325: }
326:
327: /** Internal node processor interface. */
328: private static interface Processor {
329: /**
330: * Processes a node.
331: *
332: * @param location parent node
333: * @param name node name
334: *
335: * @return new node
336: *
337: * @throws IOException if processing fails
338: */
339: Node process(ListNode location, String name) throws IOException;
340: }
341:
342: /** Performs an operation.
343: *
344: * @param uri uri to perform operation at
345: * @param processor processor which performs an operation
346: *
347: * @return new node
348: *
349: * @throws IOException if the operation fails
350: */
351: private Node process(final String uri, final Processor processor)
352: throws IOException {
353: final String[] path = splitUri(uri);
354: ListNode location = root;
355: for (int i = 0; i < path.length - 1; i++) {
356: final Node node = location.get(path[i]);
357: if ((node == null) || !(node instanceof ListNode)) {
358: reportURIProblem(uri, "no such node");
359: }
360: location = (ListNode) node;
361: }
362: return processor.process(location, path[path.length - 1]);
363: }
364:
365: /** Path elements separator. */
366: private static final char URI_SEPARATOR = '/';
367:
368: /**
369: * Validates a path.
370: *
371: * @param path path to validate
372: * @param uri original uri
373: */
374: private static void validateElements(final String[] path,
375: final String uri) {
376: // TBD: more validation
377: if (path.length == 0) {
378: reportURIProblem(uri, "no elements in the URI");
379: }
380: if (!path[0].equals(".")) {
381: reportURIProblem(uri, "1st element must be '.'");
382: }
383: for (int i = 1; i < path.length; i++) {
384: final String e = path[i];
385: if (e.length() == 0) {
386: reportURIProblem(uri, "empty elements are not allowed");
387: }
388: if (e.equals(".")) {
389: reportURIProblem(uri,
390: "'.' is only allowed as 1st element");
391: }
392: }
393: }
394:
395: /**
396: * Reports a problematic uri.
397: *
398: * @param uri problematic uri
399: * @param message message
400: */
401: private static void reportURIProblem(final String uri,
402: final String message) {
403: throw new JUMPStoreRuntimeException(message + " [uri = " + uri
404: + "]");
405: }
406:
407: /**
408: * Splits an URI into path elements.
409: *
410: * @param uri <code>URI</code> to split
411: *
412: * @return array of path elements
413: */
414: private static String[] splitUri(final String uri) {
415: // TBD: reuse splitting
416: final Vector elements = new Vector();
417: int pos = 0;
418: for (;;) {
419: final int p = uri.indexOf(URI_SEPARATOR, pos);
420: if (p == -1) {
421: elements.add(uri.substring(pos));
422: final String[] path = new String[elements.size()];
423: elements.toArray(path);
424: validateElements(path, uri);
425: return path;
426: }
427: elements.add(uri.substring(pos, p));
428: pos = p + 1;
429: }
430: }
431: }
|