diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/image/ConvolveTester.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/ConvolveTester.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/image/ConvolveTester.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/ConvolveTester.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/image/EasyImage.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/EasyImage.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/image/EasyImage.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/EasyImage.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java new file mode 100644 index 00000000..6cd09ae4 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java @@ -0,0 +1,255 @@ +package com.twelvemonkeys.image; + +import javax.swing.*; +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Random; + +/** + * MappedBufferImage + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: MappedBufferImage.java,v 1.0 May 26, 2010 5:07:01 PM haraldk Exp$ + */ +public class MappedBufferImage extends BufferedImage { + private static final boolean ALPHA = true; + + public MappedBufferImage(ColorModel cm, MappedFileRaster raster, boolean isRasterPremultiplied) { + super(cm, raster, isRasterPremultiplied, null); + } + + public static void main(String[] args) throws IOException { + int w = args.length > 0 ? Integer.parseInt(args[0]) : 6000; + int h = args.length > 1 ? Integer.parseInt(args[1]) : (args.length > 0 ? w * 2 / 3 : 4000); + + DataBuffer buffer = new MappedFileBuffer(w, h, ALPHA ? 4 : 3, 1); + + // Mix in some nice colors + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int r = (int) ((x * y * 255.0) / (h * w)); + int g = (int) (((w - x) * y * 255.0) / (h * w)); + int b = (int) ((x * (h - y) * 255.0) / (h * w)); + + int off = (y * w + x) * (ALPHA ? 4 : 3); + + if (ALPHA) { + int a = (int) (((w - x) * (h - y) * 255.0) / (h * w)); + buffer.setElem(off++, 255 - a); + } + + buffer.setElem(off++, b); + buffer.setElem(off++, g); + buffer.setElem(off, r); + + } + } + + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ComponentColorModel model = new ComponentColorModel(cs, ALPHA, false, ALPHA ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_BYTE); + BufferedImage image = new MappedBufferImage(model, new MappedFileRaster(w, h, buffer), false); + + // Add some random dots (get out the coffee) +// int s = 300; +// int ws = w / s; +// int hs = h / s; +// +// Color[] colors = new Color[] { +// Color.WHITE, Color.ORANGE, Color.BLUE, Color.MAGENTA, Color.BLACK, Color.RED, Color.CYAN, +// Color.GRAY, Color.GREEN, Color.YELLOW, Color.PINK, Color.LIGHT_GRAY, Color.DARK_GRAY +// }; +// +// Random r = new Random(); +// +// for (int y = 0; y < hs - 1; y++) { +// for (int x = 0; x < ws - 1; x++) { +// Graphics2D g = image.getSubimage(x * s, y * s, 2 * s, 2 * s).createGraphics(); +// try { +// g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); +// g.setComposite(AlphaComposite.SrcOver.derive(r.nextFloat())); +// g.setColor(colors[r.nextInt(colors.length)]); +// int o = r.nextInt(s) + s / 10; +// int c = (2 * s - o) / 2; +// g.fillOval(c, c, o, o); +// } +// finally { +// g.dispose(); +// } +// } +// } + + System.out.println("image = " + image); + + JFrame frame = new JFrame(String.format("Test [%s x %s] (%s)", w, h, toHumanReadableSize(w * h * (ALPHA ? 4 : 3)))); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + JScrollPane scroll = new JScrollPane(new ImageComponent(image)); + scroll.setBorder(BorderFactory.createEmptyBorder()); + scroll.getViewport().setScrollMode(JViewport.SIMPLE_SCROLL_MODE); + scroll.getViewport().setDoubleBuffered(false); + frame.add(scroll); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + private static String toHumanReadableSize(long size) { + return String.format("%,d MB", (int) (size / (double) (1024L << 10))); + } + + private static class ImageComponent extends JComponent implements Scrollable { + private final BufferedImage image; + private final Paint texture; + + public ImageComponent(BufferedImage image) { + setDoubleBuffered(false); + this.image = image; + + texture = createTexture(); + } + + private static Paint createTexture() { + GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); + BufferedImage pattern = graphicsConfiguration.createCompatibleImage(20, 20); + Graphics2D g = pattern.createGraphics(); + + try { + g.setColor(Color.LIGHT_GRAY); + g.fillRect(0, 0, pattern.getWidth(), pattern.getHeight()); + g.setColor(Color.GRAY); + g.fillRect(0, 0, pattern.getWidth() / 2, pattern.getHeight() / 2); + g.fillRect(pattern.getWidth() / 2, pattern.getHeight() / 2, pattern.getWidth() / 2, pattern.getHeight() / 2); + } + finally { + g.dispose(); + } + + return new TexturePaint(pattern, new Rectangle(pattern.getWidth(), pattern.getHeight())); + } + + @Override + protected void paintComponent(Graphics g) { + Insets insets = getInsets(); + + // We ant to paint only the visible part of the image + Rectangle rect = getVisibleRect(); + + Graphics2D g2 = (Graphics2D) g; + g2.setPaint(texture); + g2.fillRect(rect.x, rect.y, rect.width, rect.height); + + try { + // Paint slices of the image, to preserve memory + // Make slices wide to conform to memory alignment of buffer + int sliceHeight = 200; + int slices = rect.height / sliceHeight; + for (int i = 0; i <= slices; i++) { + int h = i == slices ? Math.min(sliceHeight, image.getHeight() - (rect.y + i * sliceHeight)) : sliceHeight; + if (h == 0) { + break; + } + BufferedImage img = image.getSubimage(rect.x, rect.y + i * sliceHeight, rect.width, h); + g2.drawImage(img, insets.left + rect.x, insets.top + rect.y + i * sliceHeight, null); + } +// BufferedImage img = image.getSubimage(rect.x, rect.y, rect.width, rect.height); +// g2.drawImage(img, insets.left + rect.x, insets.top + rect.y, null); + } + catch (NullPointerException e) { + e.printStackTrace(); + // Happens whenever apple.awt.OSXCachingSufraceManager runs out of memory + repaint(); // NOTE: Will cause a brief flash while the component is redrawn + } + } + + @Override + public Dimension getPreferredSize() { + Insets insets = getInsets(); + return new Dimension(image.getWidth() + insets.left + insets.right, image.getHeight() + insets.top + insets.bottom); + } + + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 10; + } + + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + switch (orientation) { + case SwingConstants.HORIZONTAL: + return visibleRect.width * 3 / 4; + case SwingConstants.VERTICAL: + default: + return visibleRect.height * 3 / 4; + } + } + + public boolean getScrollableTracksViewportWidth() { + return false; + } + + public boolean getScrollableTracksViewportHeight() { + return false; + } + } + + private static class MappedFileBuffer extends DataBuffer { + final ByteBuffer buffer; + + public MappedFileBuffer(final int width, final int height, final int numComponents, final int numBanks) throws IOException { + super(DataBuffer.TYPE_BYTE, width * height * numComponents, numBanks); + + if (size < 0) { + throw new IllegalArgumentException("Integer overflow"); + } + + File tempFile = File.createTempFile(String.format("%s-", getClass().getSimpleName()), ".tmp"); + tempFile.deleteOnExit(); + + RandomAccessFile raf = new RandomAccessFile(tempFile, "rw"); + raf.setLength(size * banks); + FileChannel channel = raf.getChannel(); + + // Map entire file into memory, let OS virtual memory/paging do the heavy lifting + buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, size * banks); + + // According to the docs, we can safely close the channel and delete the file now + channel.close(); + + if (!tempFile.delete()) { + System.err.println("Could not delete temp file: " + tempFile.getAbsolutePath()); + } + } + + @Override + public int getElem(int bank, int i) { + return buffer.get(bank * size + i); + } + + @Override + public void setElem(int bank, int i, int val) { + buffer.put(bank * size + i, (byte) val); + } + } + + private static class MappedFileRaster extends WritableRaster { + public MappedFileRaster(int w, int h, DataBuffer buffer) { + super( + new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, ALPHA ? 4 : 3, w * (ALPHA ? 4 : 3), ALPHA ? new int[]{3, 2, 1, 0} : new int[]{2, 1, 0}), + buffer, new Point() + ); + } + + @Override + public String toString() { + return String.format("%s@%s: w = %s h = %s", getClass().getSimpleName(), System.identityHashCode(this), getWidth(), getHeight()); + } + } +} diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/image/SubsampleTester.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/SubsampleTester.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/image/SubsampleTester.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/SubsampleTester.java diff --git a/common/common-image/src/main/java/com/twelvemonkeys/image/inv_cmap.c b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/inv_cmap.c similarity index 100% rename from common/common-image/src/main/java/com/twelvemonkeys/image/inv_cmap.c rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/inv_cmap.c diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/imageio/LayerSupport.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/imageio/LayerSupport.java new file mode 100644 index 00000000..4f370ab5 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/imageio/LayerSupport.java @@ -0,0 +1,35 @@ +package com.twelvemonkeys.imageio; + +import javax.imageio.ImageReadParam; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * LayerSupport + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: LayerSupport.java,v 1.0 Oct 29, 2009 12:46:00 AM haraldk Exp$ + */ +public interface LayerSupport { + // TODO: ..or maybe we should just allow a parameter to the PSDImageReadParam to specify layer? + // - then, how do we get the number of layers, dimension and offset? + + // boolean readerSupportsLayers(), always true if the interface is implemented + + int getNumLayers(int pImageIndex) throws IOException; + + boolean hasLayers(int pImageIndex) throws IOException; + + BufferedImage readLayer(int pImageIndex, int pLayerIndex, ImageReadParam pParam) throws IOException; + + int getLayerWidth(int pImageIndex, int pLayerIndex) throws IOException; + + int getLayerHeight(int pImageIndex, int pLayerIndex) throws IOException; + + // ? + Point getLayerOffset(int pImageIndex, int pLayerIndex) throws IOException; + + // TODO: Blend modes.. Layer meta data? +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileLockingTest.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileLockingTest.java new file mode 100755 index 00000000..101a1179 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileLockingTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.io; + +import java.io.*; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; + +/** + * FileLockingTest + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: FileLockingTest.java,v 1.0 May 12, 2009 7:15:38 PM haraldk Exp$ + */ +public class FileLockingTest { + public static void main(final String[] pArgs) throws IOException { + FileChannel channel = new RandomAccessFile(pArgs[0], "rw").getChannel(); + FileLock lock = channel.tryLock(0, Long.MAX_VALUE, pArgs.length <= 1 || !"false".equalsIgnoreCase(pArgs[1])); // Shared lock for entire file + + System.out.println("lock: " + lock); + + if (lock != null) { + System.in.read(); + + InputStream stream = Channels.newInputStream(channel); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + else { + System.out.println("Already locked"); + } + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileMonitor.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileMonitor.java new file mode 100755 index 00000000..84584c78 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileMonitor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2009, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.io; + +import java.io.File; + +/** + * Allows monitoring a file on the file system. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: FileMonitor.java,v 1.0 May 12, 2009 6:58:55 PM haraldk Exp$ + */ +public class FileMonitor { + + + + /** + * Listens for changes to a file. + * + */ + public interface FileChangeListener { + + public void fileCreated(File pFile) throws Exception; // TODO: Is this a good thing? + + public void fileUpdated(File pFile) throws Exception; + + public void fileDeleted(File pFile) throws Exception; + } + +} diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/DuckType.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/DuckType.java new file mode 100755 index 00000000..97eb33d1 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/DuckType.java @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.lang; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +/** + * “If it walks like a duck, looks like a duck, quacks like a duck, it must be…”. + *
+ * Based on an idea found at + * The Visual Editor + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/DuckType.java#1 $ + * + * @see java.lang.reflect.Proxy + */ +public final class DuckType { + /* + EXAMPLE: + public ImageMgr(Object receiver, Image image) { + if (!DuckType.instanceOf(IImageHolder.class, receiver)) { + throw new ClassCastException("Cannot implement IImageHolder"); + } + + this.image = image; + + IImageHolder imageHolder = (IImageHolder) DuckType.implement(IImageHolder.class, receiver); + imageHolder.setImage(image); + imageHolder.addDisposeListener(this); + } + */ + + // TODO: Implement some weak caching of proxy classes and instances + // TODO: Make the proxy classes serializable... + + private DuckType() {} + + public static boolean instanceOf(Class pInterface, Object pObject) { + return instanceOf(new Class[] {pInterface}, new Object[] {pObject}); + } + + public static boolean instanceOf(Class[] pInterfaces, Object pObject) { + return instanceOf(pInterfaces, new Object[] {pObject}); + } + + public static boolean instanceOf(final Class[] pInterfaces, final Object[] pObjects) { + // Get all methods of all Class in pInterfaces, and see if pObjects has + // matching implementations + + // TODO: Possible optimization: If any of the interfaces are implemented + // by one of the objects' classes, we don't need to find every method... + + for (int i = 0; i < pInterfaces.length; i++) { + Class interfce = pInterfaces[i]; + + Method[] methods = interfce.getMethods(); + + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + + //if (findMethodImplementation(method, getClasses(pObjects)) < 0) { + //if (findMethodImplementation(method, getClasses(pObjects)) == null) { + if (findMethodImplementation(method, pObjects) == null) { + return false; + } + } + } + + return true; + } + + // TODO: Might be moved to ReflectUtil + private static Class[] getClasses(final Object[] pObjects) { + Class[] classes = new Class[pObjects.length]; + + for (int i = 0; i < pObjects.length; i++) { + classes[i] = pObjects[i].getClass(); + } + + return classes; + } + + /** + * Searches for a class that has a method maching the given signature. + * Returns the index of the class in the {@code pClasses} array that has a + * matching method. + * If there is more than one class that has a matching method the first + * index will be returned. + * If there is no match in any of the classes, {@code -1} is returned. + * + * @param pMethod + * @param pObjects + * + * @return the first index of the object in the {@code pObjects} array that + * has a matching method, or {@code -1} if none was found. + */ + // TODO: Might be moved to ReflectUtil + //static int findMethodImplementation(final Method pMethod, final Class[] pClasses) { + static MethodProxy findMethodImplementation(final Method pMethod, final Object[] pObjects) { + // TODO: Optimization: Each getParameterTypes() invokation creates a + // new clone of the array. If we do it once and store the refs, that + // would be a good idea + + // Optimization, don't test class more than once + Set tested = new HashSet(pObjects.length); + + for (int i = 0; i < pObjects.length; i++) { + Class cls = pObjects[i].getClass(); + + if (tested.contains(cls)) { + continue; + } + else { + tested.add(cls); + } + + try { + // NOTE: This test might be too restrictive + // We could actually go ahead with + // supertype parameters or subtype return types... + // However, we should only do that after we have tried all + // classes for direct mathces. + Method method = cls.getMethod(pMethod.getName(), + pMethod.getParameterTypes()); + + if (matches(pMethod, method)) { + //return i; + // TODO: This is a waste of time if we are only testing if there's a method here... + return new MethodProxy(method, pObjects[i]); + } + } + catch (NoSuchMethodException e) { + // Ingore + } + } + + if (hasSuperTypes(pMethod.getParameterTypes())) { + SortedSet uniqueMethods = new TreeSet(); + for (int i = 0; i < pObjects.length; i++) { + Class cls = pObjects[i].getClass(); + + Method[] methods = cls.getMethods(); + + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + + // Now, for each method + // 1 test if the name matches + // 2 test if the parameter types match for superclass + // 3 Test return types for assignability? + if (pMethod.getName().equals(method.getName()) + && isAssignableFrom(method.getParameterTypes(), pMethod.getParameterTypes()) + && pMethod.getReturnType().isAssignableFrom(method.getReturnType())) { + // 4 TODO: How to find the most specific match?! + //return new MethodProxy(method, pObjects[i]); + uniqueMethods.add(new MethodProxy(method, pObjects[i])); + } + } + } + if (uniqueMethods.size() == 1) { + return (MethodProxy) uniqueMethods.first(); + } + else { + // TODO: We need to figure out what method is the best match.. + } + } + + //return -1; + return null; + } + + private static boolean isAssignableFrom(Class[] pTypes, Class[] pSubTypes) { + if (pTypes.length != pSubTypes.length) { + return false; + } + + for (int i = 0; i < pTypes.length; i++) { + if (!pTypes[i].isAssignableFrom(pSubTypes[i])) { + return false; + } + + } + return true; + } + + private static boolean hasSuperTypes(Class[] pParameterTypes) { + for (int i = 0; i < pParameterTypes.length; i++) { + Class type = pParameterTypes[i]; + + if (type != Object.class + && (type.isInterface() || type.getSuperclass() != null)) { + return true; + } + } + return false; + } + + /** + * Tests two {@code Method}s for match. + * That is, they have same name and equal parameters. + * + * @param pLeft + * @param pRight + * + * @return + * + * @see Method#equals(Object) + */ + private static boolean matches(Method pLeft, Method pRight) { + if (pLeft == pRight) { + return true; + } + else if (pLeft.getName().equals(pRight.getName()) + && pLeft.getReturnType().isAssignableFrom(pRight.getReturnType())) { + + // Avoid unnecessary cloning + Class[] params1 = pLeft.getParameterTypes(); + Class[] params2 = pRight.getParameterTypes(); + if (params1.length == params2.length) { + for (int i = 0; i < params1.length; i++) { + if (params1[i] != params2[i]) { + return false; + } + } + return true; + } + } + + return false; + } + + public static Object implement(Class pInterface, Object pObject) throws NoMatchingMethodException { + return implement(new Class[] {pInterface}, new Object[] {pObject}, false); + } + + public static Object implement(Class[] pInterfaces, Object pObject) throws NoMatchingMethodException { + return implement(pInterfaces, new Object[] {pObject}, false); + } + + // TODO: What about the interfaces pObjects allready implements? + // TODO: Use first object as "identity"? Allow user to supply "indentity" + // that is not exposed as part of the implemented interfaces? + public static Object implement(final Class[] pInterfaces, final Object[] pObjects) throws NoMatchingMethodException { + return implement(pInterfaces, pObjects, false); + } + + public static Object implement(final Class[] pInterfaces, final Object[] pObjects, boolean pStubAbstract) throws NoMatchingMethodException { + Map delegates = new HashMap(pObjects.length * 10); + + for (int i = 0; i < pInterfaces.length; i++) { + Class interfce = pInterfaces[i]; + + Method[] methods = interfce.getMethods(); + + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + + //int idx = findMethodImplementation(method, getClasses(pObjects)); + //Method impl = findMethodImplementation(method, getClasses(pObjects)); + MethodProxy impl = findMethodImplementation(method, pObjects); + //if (idx < 0) { + if (impl == null) { + // TODO: Maybe optionally create stubs that fails when invoked?! + if (pStubAbstract) { + impl = MethodProxy.createAbstract(method); + } + else { + throw new NoMatchingMethodException(interfce.getName() + "." + + method.getName() + + parameterTypesToString(method.getParameterTypes())); + } + } + + if (!delegates.containsKey(method)) { + // TODO: Must find the correct object... + //delegates.put(method, new MethodProxy(method, pObjects[idx])); + delegates.put(method, impl); + } + } + } + + // TODO: It's probably not good enough to use the current context class loader + // TODO: Either let user specify classloader directly + // TODO: ...or use one of the classloaders from pInterfaces or pObjects + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), + pInterfaces, new DelegationHandler(delegates)); + } + + private static String parameterTypesToString(Class[] pTypes) { + StringBuilder buf = new StringBuilder(); + buf.append("("); + if (pTypes != null) { + for (int i = 0; i < pTypes.length; i++) { + if (i > 0) { + buf.append(", "); + } + Class c = pTypes[i]; + buf.append((c == null) ? "null" : c.getName()); + } + } + buf.append(")"); + return buf.toString(); + } + + static class MethodProxy { + private final Method mMethod; + private final Object mDelegate; + + private final static Object ABSTRACT_METHOD_DELEGATE = new Object() { + }; + + public static MethodProxy createAbstract(Method pMethod) { + return new MethodProxy(pMethod, ABSTRACT_METHOD_DELEGATE) { + public Object invoke(Object[] pArguments) throws Throwable { + throw abstractMehthodError(); + } + }; + } + + public MethodProxy(Method pMethod, Object pDelegate) { + if (pMethod == null) { + throw new IllegalArgumentException("method == null"); + } + if (pDelegate == null) { + throw new IllegalArgumentException("delegate == null"); + } + + mMethod = pMethod; + mDelegate = pDelegate; + } + + public Object invoke(Object[] pArguments) throws Throwable { + try { + return mMethod.invoke(mDelegate, pArguments); + } + catch (IllegalAccessException e) { + throw new Error(e); // This is an error in the impl + } + catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + Error abstractMehthodError() { + return new AbstractMethodError(mMethod.toString()); + } + + public int hashCode() { + return mMethod.hashCode() ^ mDelegate.hashCode(); + } + + public boolean equals(Object pOther) { + if (pOther == this) { + return true; + } + if (pOther instanceof MethodProxy) { + MethodProxy other = (MethodProxy) pOther; + return mMethod.equals(other.mMethod) && mDelegate.equals(other.mDelegate); + } + return false; + } + + public String toString() { + return mMethod.toString() + mDelegate.toString(); + } + } + + public static class NoMatchingMethodException extends IllegalArgumentException { + public NoMatchingMethodException() { + super(); + } + + public NoMatchingMethodException(String s) { + super(s); + } + + public NoMatchingMethodException(Exception e) { + super(e.getMessage()); + initCause(e); + } + } + + // TODO: Must handle identity... + // TODO: equals/hashCode + // TODO: Allow clients to pass in Identity subclasses? + private static class DelegationHandler implements InvocationHandler { + private final Map mDelegates; + + public DelegationHandler(Map pDelegates) { + mDelegates = pDelegates; + } + + public final Object invoke(Object pProxy, Method pMethod, Object[] pArguments) + throws Throwable + { + if (pMethod.getDeclaringClass() == Object.class) { + // Intercept equals/hashCode/toString + String name = pMethod.getName(); + if (name.equals("equals")) { + return proxyEquals(pProxy, pArguments[0]); + } + else if (name.equals("hashCode")) { + return proxyHashCode(pProxy); + } + else if (name.equals("toString")) { + return proxyToString(pProxy); + } + + // Other methods are handled by their default Object + // implementations + return pMethod.invoke(this, pArguments); + } + + MethodProxy mp = (MethodProxy) mDelegates.get(pMethod); + + return mp.invoke(pArguments); + } + + protected Integer proxyHashCode(Object pProxy) { + //return new Integer(System.identityHashCode(pProxy)); + return new Integer(mDelegates.hashCode()); + } + + protected Boolean proxyEquals(Object pProxy, Object pOther) { + return pProxy == pOther || + (Proxy.isProxyClass(pOther.getClass()) + && Proxy.getInvocationHandler(pOther) instanceof DelegationHandler + && ((DelegationHandler) Proxy.getInvocationHandler(pOther)).mDelegates.equals(mDelegates)) + ? Boolean.TRUE : Boolean.FALSE; + } + + protected String proxyToString(Object pProxy) { + return pProxy.getClass().getName() + '@' + + Integer.toHexString(pProxy.hashCode()); + } + } +} diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseConnection.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseConnection.java new file mode 100755 index 00000000..efb5b60f --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseConnection.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + +import com.twelvemonkeys.lang.SystemUtil; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +/** + * A class that holds a JDBC Connection. The class can be configured by a + * properties file. However, the approach is rather lame, and only lets you + * configure one connection... + * + * Tested with jConnect (Sybase), I-net Sprinta2000 (MS SQL) and Oracle. + * + * @todo be able to register more drivers, trough properties and runtime + * @todo be able to register more connections, trough properties and runtime + * + * Example properties file + * # filename: com.twelvemonkeys.sql.DatabaseConnection.properties + * driver=com.inet.tds.TdsDriver + * url=jdbc:inetdae7:127.0.0.1:1433?database\=mydb + * user=scott + * password=tiger + * # What do you expect, really? + * logDebug=true + * + * @author Philippe Béal (phbe@iconmedialab.no) + * @author Harald Kuhr (haraldk@iconmedialab.no) + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/DatabaseConnection.java#1 $ + * + * @todo Use org.apache.commons.logging instead of proprietary logging. + * + */ + +public class DatabaseConnection { + + // Default driver + public final static String DEFAULT_DRIVER = "NO_DRIVER"; + // Default URL + public final static String DEFAULT_URL = "NO_URL"; + + protected static String mDriver = null; + protected static String mUrl = null; + + // Default debug is true + // private static boolean debug = true; + + protected static Properties mConfig = null; + //protected static Log mLog = null; + protected static Log mLog = null; + + protected static boolean mInitialized = false; + + // Must be like this... + // http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html :-) + private static DatabaseConnection sInstance = new DatabaseConnection(); + + /** + * Creates the DatabaseConnection. + */ + + private DatabaseConnection() { + init(); + } + + /** + * Gets the single DatabaseConnection instance. + */ + + protected static DatabaseConnection getInstance() { + /* + if (sInstance == null) { + sInstance = new DatabaseConnection(); + sInstance.init(); + } + */ + return sInstance; + } + + /** + * Initializes the DatabaseConnection, called from the constructor. + * + * @exception IllegalStateException if an attempt to call init() is made + * after the instance is allready initialized. + */ + + protected synchronized void init() { + // Make sure init is executed only once! + if (mInitialized) { + throw new IllegalStateException("init() may only be called once!"); + } + + mInitialized = true; + + try { + mConfig = SystemUtil.loadProperties(DatabaseConnection.class); + } + catch (FileNotFoundException fnf) { + // Ignore + } + catch (IOException ioe) { + //LogFactory.getLog(getClass()).error("Caught IOException: ", ioe); + new Log(this).logError(ioe); + //ioe.printStackTrace(); + } + finally { + if (mConfig == null) { + mConfig = new Properties(); + } + } + + mLog = new Log(this, mConfig); + //mLog = LogFactory.getLog(getClass()); + // debug = new Boolean(config.getProperty("debug", "true")).booleanValue(); + // config.list(System.out); + + mDriver = mConfig.getProperty("driver", DEFAULT_DRIVER); + mUrl = mConfig.getProperty("url", DEFAULT_URL); + } + + /** + * Gets the default JDBC Connection. The connection is configured through + * the properties file. + * + * @return the default jdbc Connection + */ + + public static Connection getConnection() { + return getConnection(null, null, getInstance().mUrl); + } + + /** + * Gets a JDBC Connection with the given parameters. The connection is + * configured through the properties file. + * + * @param pUser the database user name + * @param pPassword the password of the database user + * @param pURL the url to connect to + * + * @return a jdbc Connection + */ + + public static Connection getConnection(String pUser, + String pPassword, + String pURL) { + return getInstance().getConnectionInstance(pUser, pPassword, pURL); + + } + + /** + * Gets a JDBC Connection with the given parameters. The connection is + * configured through the properties file. + * + * @param pUser the database user name + * @param pPassword the password of the database user + * @param pURL the url to connect to + * + * @return a jdbc Connection + */ + + protected Connection getConnectionInstance(String pUser, + String pPassword, + String pURL) { + Properties props = (Properties) mConfig.clone(); + + if (pUser != null) { + props.put("user", pUser); + } + if (pPassword != null) { + props.put("password", pPassword); + } + + // props.list(System.out); + + try { + // Load & register the JDBC Driver + if (!DEFAULT_DRIVER.equals(mDriver)) { + Class.forName(mDriver).newInstance(); + } + + Connection conn = DriverManager.getConnection(pURL, props); + + if (mLog.getLogDebug()) { + //if (mLog.isDebugEnabled()) { + DatabaseMetaData dma = conn.getMetaData(); + mLog.logDebug("Connected to " + dma.getURL()); + mLog.logDebug("Driver " + dma.getDriverName()); + mLog.logDebug("Version " + dma.getDriverVersion()); + + //mLog.debug("Connected to " + dma.getURL()); + //mLog.debug("Driver " + dma.getDriverName()); + //mLog.debug("Version " + dma.getDriverVersion()); + } + + return conn; + } + catch (Exception e) { + mLog.logError(e.getMessage()); + + // Get chained excpetions + if (e instanceof SQLException) { + SQLException sqle = (SQLException) e; + while ((sqle = sqle.getNextException()) != null) { + mLog.logWarning(sqle); + } + } + } + return null; + } + +} + diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java new file mode 100755 index 00000000..63188ded --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + +import java.io.Serializable; + +/** + * DatabaseProduct + * + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java#1 $ + */ +public final class DatabaseProduct implements Serializable { + private static final String UNKNOWN_NAME = "Unknown"; + private static final String GENERIC_NAME = "Generic"; + private static final String CACHE_NAME = "Caché"; + private static final String DB2_NAME = "DB2"; + private static final String MSSQL_NAME = "MSSQL"; + private static final String ORACLE_NAME = "Oracle"; + private static final String POSTGRESS_NAME = "PostgreSQL"; + private static final String SYBASE_NAME = "Sybase"; + + /*public*/ static final DatabaseProduct UNKNOWN = new DatabaseProduct(UNKNOWN_NAME); + public static final DatabaseProduct GENERIC = new DatabaseProduct(GENERIC_NAME); + public static final DatabaseProduct CACHE = new DatabaseProduct(CACHE_NAME); + public static final DatabaseProduct DB2 = new DatabaseProduct(DB2_NAME); + public static final DatabaseProduct MSSQL = new DatabaseProduct(MSSQL_NAME); + public static final DatabaseProduct ORACLE = new DatabaseProduct(ORACLE_NAME); + public static final DatabaseProduct POSTGRES = new DatabaseProduct(POSTGRESS_NAME); + public static final DatabaseProduct SYBASE = new DatabaseProduct(SYBASE_NAME); + + private static final DatabaseProduct[] VALUES = { + GENERIC, CACHE, DB2, MSSQL, ORACLE, POSTGRES, SYBASE, + }; + + private static int sNextOrdinal = -1; + private final int mOrdinal = sNextOrdinal++; + + private final String mKey; + + private DatabaseProduct(String pName) { + mKey = pName; + } + + static int enumSize() { + return sNextOrdinal; + } + + final int id() { + return mOrdinal; + } + + final String key() { + return mKey; + } + + public String toString() { + return mKey + " [id=" + mOrdinal+ "]"; + } + + /** + * Gets the {@code DatabaseProduct} known by the given name. + * + * @param pName + * @return the {@code DatabaseProduct} known by the given name + * @throws IllegalArgumentException if there's no such name + */ + public static DatabaseProduct resolve(String pName) { + if ("ANSI".equalsIgnoreCase(pName) || GENERIC_NAME.equalsIgnoreCase(pName)) { + return GENERIC; + } + else if ("Cache".equalsIgnoreCase(pName) || CACHE_NAME.equalsIgnoreCase(pName)) { + return CACHE; + } + else if (DB2_NAME.equalsIgnoreCase(pName)) { + return DB2; + } + else if (MSSQL_NAME.equalsIgnoreCase(pName)) { + return MSSQL; + } + else if (ORACLE_NAME.equalsIgnoreCase(pName)) { + return ORACLE; + } + else if ("Postgres".equalsIgnoreCase(pName) || POSTGRESS_NAME.equalsIgnoreCase(pName)) { + return POSTGRES; + } + else if (SYBASE_NAME.equalsIgnoreCase(pName)) { + return SYBASE; + } + else { + throw new IllegalArgumentException("Unknown database product \"" + pName + + "\", try any of the known products, or \"Generic\""); + } + } + + private Object readResolve() { + return VALUES[mOrdinal]; // Canonicalize + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseReadable.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseReadable.java new file mode 100755 index 00000000..f966dc7b --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseReadable.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + +import java.util.Hashtable; + +/** + * Interface for classes that is to be read from a database, using the + * ObjectReader class. + * + * @author Harald Kuhr (haraldk@iconmedialab.no) + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/DatabaseReadable.java#1 $ + * + * @todo Use JDK logging instead of proprietary logging. + * + */ +public interface DatabaseReadable { + + /** + * Gets the unique identifier of this DatabaseReadable object. + * + * @return An object that uniqely identifies this DatabaseReadable. + */ + + public Object getId(); + + /** + * Sets the unique identifier of this DatabaseReadable object. + * + * @param id An object that uniqely identifies this DatabaseReadable. + */ + + public void setId(Object id); + + /** + * Gets the object to database mapping of this DatabaseReadable. + * + * @return A Hashtable cotaining the database mapping for this + * DatabaseReadable. + */ + + public Hashtable getMapping(); +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java new file mode 100755 index 00000000..e9847d7f --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + +/** + * AbstractHelper + * + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java#1 $ + */ +public abstract class JDBCHelper { + + private static JDBCHelper[] sHelpers = new JDBCHelper[DatabaseProduct.enumSize()]; + + static { + DatabaseProduct product = DatabaseProduct.resolve(System.getProperty("com.twelvemonkeys.sql.databaseProduct", "Generic")); + sHelpers[0] = createInstance(product); + } + + private JDBCHelper() { + } + + private static JDBCHelper createInstance(DatabaseProduct pProduct) { + // Get database name + // Instantiate helper + if (pProduct == DatabaseProduct.GENERIC) { + return new GenericHelper(); + } + else if (pProduct == DatabaseProduct.CACHE) { + return new CacheHelper(); + } + else if (pProduct == DatabaseProduct.DB2) { + return new DB2Helper(); + } + else if (pProduct == DatabaseProduct.MSSQL) { + return new MSSQLHelper(); + } + else if (pProduct == DatabaseProduct.ORACLE) { + return new OracleHelper(); + } + else if (pProduct == DatabaseProduct.POSTGRES) { + return new PostgreSQLHelper(); + } + else if (pProduct == DatabaseProduct.SYBASE) { + return new SybaseHelper(); + } + else { + throw new IllegalArgumentException("Unknown database product, try any of the known products, or \"generic\""); + } + } + + public final static JDBCHelper getInstance() { + return sHelpers[0]; + } + + public final static JDBCHelper getInstance(DatabaseProduct pProuct) { + JDBCHelper helper = sHelpers[pProuct.id()]; + if (helper == null) { + // This is ok, iff sHelpers[pProuct] = helper is an atomic op... + synchronized (sHelpers) { + helper = sHelpers[pProuct.id()]; + if (helper == null) { + helper = createInstance(pProuct); + sHelpers[pProuct.id()] = helper; + } + } + } + return helper; + } + + // Abstract or ANSI SQL implementations of different stuff + + public String getDefaultDriverName() { + return ""; + } + + public String getDefaultURL() { + return "jdbc:{$DRIVER}://localhost:{$PORT}/{$DATABASE}"; + } + + // Vendor specific concrete implementations + + static class GenericHelper extends JDBCHelper { + // Nothing here + } + + static class CacheHelper extends JDBCHelper { + public String getDefaultDriverName() { + return "com.intersys.jdbc.CacheDriver"; + } + + public String getDefaultURL() { + return "jdbc:Cache://localhost:1972/{$DATABASE}"; + } + } + + static class DB2Helper extends JDBCHelper { + public String getDefaultDriverName() { + return "COM.ibm.db2.jdbc.net.DB2Driver"; + } + + public String getDefaultURL() { + return "jdbc:db2:{$DATABASE}"; + } + } + + static class MSSQLHelper extends JDBCHelper { + public String getDefaultDriverName() { + return "com.microsoft.jdbc.sqlserver.SQLServerDriver"; + } + + public String getDefaultURL() { + return "jdbc:microsoft:sqlserver://localhost:1433;databasename={$DATABASE};SelectMethod=cursor"; + } + } + + static class OracleHelper extends JDBCHelper { + public String getDefaultDriverName() { + return "oracle.jdbc.driver.OracleDriver"; + } + + public String getDefaultURL() { + return "jdbc:oracle:thin:@localhost:1521:{$DATABASE}"; + } + } + + static class PostgreSQLHelper extends JDBCHelper { + public String getDefaultDriverName() { + return "org.postgresql.Driver"; + } + + public String getDefaultURL() { + return "jdbc:postgresql://localhost/{$DATABASE}"; + } + } + + static class SybaseHelper extends JDBCHelper { + public String getDefaultDriverName() { + return "com.sybase.jdbc2.jdbc.SybDriver"; + } + + public String getDefaultURL() { + return "jdbc:sybase:Tds:localhost:4100/"; + } + } +} \ No newline at end of file diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/Log.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/Log.java new file mode 100755 index 00000000..df7b51d2 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/Log.java @@ -0,0 +1,673 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + + +import com.twelvemonkeys.lang.SystemUtil; + +import java.io.*; +import java.util.Date; +import java.util.Hashtable; +import java.util.Properties; + +/** + * Class used for logging. + * The class currently supports four levels of logging (debug, warning, error + * and info). + *+ * The class maintains a cahce of OutputStreams, to avoid more than one stream + * logging to a specific file. The class should also be thread safe, in that no + * more than one instance of the class can log to the same OuputStream. + *
+ * + * WARNING: The uniqueness of logfiles is based on filenames alone, meaning + * "info.log" and "./info.log" will probably be treated as different files, + * and have different streams attatched to them. + * + *
+ * + * WARNING: The cached OutputStreams can possibly be in error state or be + * closed without warning. Should be fixed in later versions! + * + * + * @author Harald Kuhr (haraldk@iconmedialab.no) + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/Log.java#1 $ + * + * @deprecated Use the JDK java.util.logging for logging. + * This class is old and outdated, and is here only for compatibility. It will + * be removed from the library in later releases. + *
+ * All new code are strongly encouraged to use the org.apache.commons.logging + * package for logging. + * + * @see java.util.logging.Logger + * + */ + +class Log { + private static Hashtable streamCache = new Hashtable(); + + static { + streamCache.put("System.out", System.out); + streamCache.put("System.err", System.err); + } + + private static Log globalLog = null; + + private String owner = null; + + private boolean logDebug = false; + private boolean logWarning = false; + private boolean logError = true; // Log errors! + private boolean logInfo = false; + + private PrintStream debugLog = null; + private PrintStream warningLog = null; + private PrintStream errorLog = null; + private PrintStream infoLog = null; + + /** + * Init global log + */ + + static { + Properties config = null; + try { + config = SystemUtil.loadProperties(Log.class); + } + catch (FileNotFoundException fnf) { + // That's okay. + } + catch (IOException ioe) { + // Not so good + log(System.err, "ERROR", Log.class.getName(), null, ioe); + } + + globalLog = new Log(new Log(), config); + + // Defaults + if (globalLog.debugLog == null) + globalLog.setDebugLog(System.out); + if (globalLog.warningLog == null) + globalLog.setWarningLog(System.err); + if (globalLog.errorLog == null) + globalLog.setErrorLog(System.err); + if (globalLog.infoLog == null) + globalLog.setInfoLog(System.out); + + // Info + globalLog.logDebug("Logging system started."); + log(globalLog.infoLog, "INFO", Log.class.getName(), + "Logging system started.", null); + } + + /** + * Internal use only + */ + + private Log() { + } + + /** + * Creates a log + */ + + public Log(Object owner) { + this.owner = owner.getClass().getName(); + } + + /** + * Creates a log + */ + + public Log(Object owner, Properties config) { + this(owner); + + if (config == null) + return; + + // Set logging levels + logDebug = new Boolean(config.getProperty("logDebug", + "false")).booleanValue(); + logWarning = new Boolean(config.getProperty("logWarning", + "false")).booleanValue(); + logError = new Boolean(config.getProperty("logError", + "true")).booleanValue(); + logInfo = new Boolean(config.getProperty("logInfo", + "true")).booleanValue(); + + // Set logging streams + String fileName; + try { + if ((fileName = config.getProperty("debugLog")) != null) + setDebugLog(fileName); + + if ((fileName = config.getProperty("warningLog")) != null) + setWarningLog(fileName); + + if ((fileName = config.getProperty("errorLog")) != null) + setErrorLog(fileName); + + if ((fileName = config.getProperty("infoLog")) != null) + setInfoLog(fileName); + } + catch (IOException ioe) { + if (errorLog == null) + setErrorLog(System.err); + logError("Could not create one or more logging streams! ", ioe); + } + } + + /** + * Checks if we log debug info + * + * @return True if logging + */ + + public boolean getLogDebug() { + return logDebug; + } + + /** + * Sets wheter we are to log debug info + * + * @param logDebug Boolean, true if we want to log debug info + */ + + public void setLogDebug(boolean logDebug) { + this.logDebug = logDebug; + } + + /** + * Checks if we globally log debug info + * + * @return True if global logging + */ + /* + public static boolean getGlobalDebug() { + return globalDebug; + } + */ + /** + * Sets wheter we are to globally log debug info + * + * @param logDebug Boolean, true if we want to globally log debug info + */ + /* + public static void setGlobalDebug(boolean globalDebug) { + Log.globalDebug = globalDebug; + } + /* + /** + * Sets the OutputStream we want to print to + * + * @param os The OutputStream we will use for logging + */ + + public void setDebugLog(OutputStream os) { + debugLog = new PrintStream(os, true); + } + + /** + * Sets the filename of the File we want to print to. Equivalent to + * setDebugLog(new FileOutputStream(fileName, true)) + * + * @param file The File we will use for logging + * @see #setDebugLog(OutputStream) + */ + + public void setDebugLog(String fileName) throws IOException { + setDebugLog(getStream(fileName)); + } + + /** + * Prints debug info to the current debugLog + * + * @param message The message to log + * @see #logDebug(String, Exception) + */ + + public void logDebug(String message) { + logDebug(message, null); + } + + /** + * Prints debug info to the current debugLog + * + * @param exception An Exception + * @see #logDebug(String, Exception) + */ + + public void logDebug(Exception exception) { + logDebug(null, exception); + } + + + /** + * Prints debug info to the current debugLog + * + * @param message The message to log + * @param exception An Exception + */ + + public void logDebug(String message, Exception exception) { + if (!(logDebug || globalLog.logDebug)) + return; + + if (debugLog != null) + log(debugLog, "DEBUG", owner, message, exception); + else + log(globalLog.debugLog, "DEBUG", owner, message, exception); + } + + // WARNING + + /** + * Checks if we log warning info + * + * @return True if logging + */ + + public boolean getLogWarning() { + return logWarning; + } + + /** + * Sets wheter we are to log warning info + * + * @param logWarning Boolean, true if we want to log warning info + */ + + public void setLogWarning(boolean logWarning) { + this.logWarning = logWarning; + } + + /** + * Checks if we globally log warning info + * + * @return True if global logging + */ + /* + public static boolean getGlobalWarning() { + return globalWarning; + } + */ + /** + * Sets wheter we are to globally log warning info + * + * @param logWarning Boolean, true if we want to globally log warning info + */ + /* + public static void setGlobalWarning(boolean globalWarning) { + Log.globalWarning = globalWarning; + } + */ + /** + * Sets the OutputStream we want to print to + * + * @param os The OutputStream we will use for logging + */ + + public void setWarningLog(OutputStream os) { + warningLog = new PrintStream(os, true); + } + + /** + * Sets the filename of the File we want to print to. Equivalent to + * setWarningLog(new FileOutputStream(fileName, true)) + * + * @param file The File we will use for logging + * @see #setWarningLog(OutputStream) + */ + + public void setWarningLog(String fileName) throws IOException { + setWarningLog(getStream(fileName)); + } + + /** + * Prints warning info to the current warningLog + * + * @param message The message to log + * @see #logWarning(String, Exception) + */ + + public void logWarning(String message) { + logWarning(message, null); + } + + /** + * Prints warning info to the current warningLog + * + * @param exception An Exception + * @see #logWarning(String, Exception) + */ + + public void logWarning(Exception exception) { + logWarning(null, exception); + } + + + /** + * Prints warning info to the current warningLog + * + * @param message The message to log + * @param exception An Exception + */ + + public void logWarning(String message, Exception exception) { + if (!(logWarning || globalLog.logWarning)) + return; + + if (warningLog != null) + log(warningLog, "WARNING", owner, message, exception); + else + log(globalLog.warningLog, "WARNING", owner, message, exception); + } + + // ERROR + + /** + * Checks if we log error info + * + * @return True if logging + */ + + public boolean getLogError() { + return logError; + } + + /** + * Sets wheter we are to log error info + * + * @param logError Boolean, true if we want to log error info + */ + + public void setLogError(boolean logError) { + this.logError = logError; + } + + /** + * Checks if we globally log error info + * + * @return True if global logging + */ + /* + public static boolean getGlobalError() { + return globalError; + } + */ + /** + * Sets wheter we are to globally log error info + * + * @param logError Boolean, true if we want to globally log error info + */ + /* + public static void setGlobalError(boolean globalError) { + Log.globalError = globalError; + } + */ + /** + * Sets the OutputStream we want to print to + * + * @param os The OutputStream we will use for logging + */ + + public void setErrorLog(OutputStream os) { + errorLog = new PrintStream(os, true); + } + + /** + * Sets the filename of the File we want to print to. Equivalent to + * setErrorLog(new FileOutputStream(fileName, true)) + * + * @param file The File we will use for logging + * @see #setErrorLog(OutputStream) + */ + + public void setErrorLog(String fileName) throws IOException { + setErrorLog(getStream(fileName)); + } + + /** + * Prints error info to the current errorLog + * + * @param message The message to log + * @see #logError(String, Exception) + */ + + public void logError(String message) { + logError(message, null); + } + + /** + * Prints error info to the current errorLog + * + * @param exception An Exception + * @see #logError(String, Exception) + */ + + public void logError(Exception exception) { + logError(null, exception); + } + + /** + * Prints error info to the current errorLog + * + * @param message The message to log + * @param exception An Exception + */ + + public void logError(String message, Exception exception) { + if (!(logError || globalLog.logError)) + return; + + if (errorLog != null) + log(errorLog, "ERROR", owner, message, exception); + else + log(globalLog.errorLog, "ERROR", owner, message, exception); + } + + // INFO + + /** + * Checks if we log info info + * + * @return True if logging + */ + + public boolean getLogInfo() { + return logInfo; + } + + /** + * Sets wheter we are to log info info + * + * @param logInfo Boolean, true if we want to log info info + */ + + public void setLogInfo(boolean logInfo) { + this.logInfo = logInfo; + } + + /** + * Checks if we globally log info info + * + * @return True if global logging + */ + /* + public static boolean getGlobalInfo() { + return globalInfo; + } + */ + /** + * Sets wheter we are to globally log info info + * + * @param logInfo Boolean, true if we want to globally log info info + */ + /* + public static void setGlobalInfo(boolean globalInfo) { + Log.globalInfo = globalInfo; + } + */ + /** + * Sets the OutputStream we want to print to + * + * @param os The OutputStream we will use for logging + */ + + public void setInfoLog(OutputStream os) { + infoLog = new PrintStream(os, true); + } + + /** + * Sets the filename of the File we want to print to. Equivalent to + * setInfoLog(new FileOutputStream(fileName, true)) + * + * @param file The File we will use for logging + * @see #setInfoLog(OutputStream) + */ + + public void setInfoLog(String fileName) throws IOException { + setInfoLog(getStream(fileName)); + } + + /** + * Prints info info to the current infoLog + * + * @param message The message to log + * @see #logInfo(String, Exception) + */ + + public void logInfo(String message) { + logInfo(message, null); + } + + /** + * Prints info info to the current infoLog + * + * @param exception An Exception + * @see #logInfo(String, Exception) + */ + + public void logInfo(Exception exception) { + logInfo(null, exception); + } + + /** + * Prints info info to the current infoLog + * + * @param message The message to log + * @param exception An Exception + */ + + public void logInfo(String message, Exception exception) { + if (!(logInfo || globalLog.logInfo)) + return; + + if (infoLog != null) + log(infoLog, "INFO", owner, message, exception); + else + log(globalLog.infoLog, "INFO", owner, message, exception); + } + + // LOG + + /** + * Internal method to get a named stream + */ + + private static OutputStream getStream(String name) throws IOException { + OutputStream os = null; + + synchronized (streamCache) { + if ((os = (OutputStream) streamCache.get(name)) != null) + return os; + + os = new FileOutputStream(name, true); + streamCache.put(name, os); + } + + return os; + } + + /** + * Internal log method + */ + + private static void log(PrintStream ps, String header, + String owner, String message, Exception ex) { + // Only allow one instance to print to the given stream. + synchronized (ps) { + // Create output stream for logging + LogStream logStream = new LogStream(ps); + + logStream.time = new Date(System.currentTimeMillis()); + logStream.header = header; + logStream.owner = owner; + + if (message != null) + logStream.println(message); + + if (ex != null) { + logStream.println(ex.getMessage()); + ex.printStackTrace(logStream); + } + } + } +} + +/** + * Utility class for logging. + * + * Minimal overloading of PrintStream + */ + +class LogStream extends PrintStream { + Date time = null; + String header = null; + String owner = null; + + public LogStream(OutputStream ps) { + super(ps); + } + + public void println(Object o) { + if (o == null) + println("null"); + else + println(o.toString()); + } + + public void println(String str) { + super.println("*** " + header + " (" + time + ", " + time.getTime() + + ") " + owner + ": " + str); + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectManager.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectManager.java new file mode 100755 index 00000000..70486f32 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectManager.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + + +import java.lang.reflect.*; +import java.util.*; +import java.sql.SQLException; +import java.sql.Connection; + +/* + Det vi trenger er en mapping mellom + - abstrakt navn/klasse/type/identifikator (tilsv. repository) + - java klasse + - selve mappingen av db kolonne/java property + + I tillegg en mapping mellom alle objektene som brukes i VM'en, og deres id'er + +*/ + +/** + * Under construction. + * + * @author Harald Kuhr (haraldk@iconmedialab.no), + * @version 0.5 + */ +public abstract class ObjectManager { + private ObjectReader mObjectReader = null; + + private WeakHashMap mLiveObjects = new WeakHashMap(); // object/id + + private Hashtable mTypes = new Hashtable(); // type name/java class + private Hashtable mMappings = new Hashtable(); // type name/mapping + + /** + * Creates an Object Manager with the default JDBC connection + */ + + public ObjectManager() { + this(DatabaseConnection.getConnection()); + } + + /** + * Creates an Object Manager with the given JDBC connection + */ + + public ObjectManager(Connection pConnection) { + mObjectReader = new ObjectReader(pConnection); + } + + + /** + * Gets the property/column mapping for a given type + */ + + protected Hashtable getMapping(String pType) { + return (Hashtable) mMappings.get(pType); + } + + /** + * Gets the class for a type + * + * @return The class for a type. If the type is not found, this method will + * throw an excpetion, and will never return null. + */ + + protected Class getType(String pType) { + Class cl = (Class) mTypes.get(pType); + + if (cl == null) { + // throw new NoSuchTypeException(); + } + + return cl; + } + + /** + * Gets a java object of the class for a given type. + */ + + protected Object getObject(String pType) + /*throws XxxException*/ { + // Get class + Class cl = getType(pType); + + // Return the new instance (requires empty public constructor) + try { + return cl.newInstance(); + } + catch (Exception e) { + // throw new XxxException(e); + throw new RuntimeException(e.getMessage()); + } + + // Can't happen + //return null; + } + + /** + * Gets a DatabaseReadable object that can be used for looking up the + * object properties from the database. + */ + + protected DatabaseReadable getDatabaseReadable(String pType) { + + return new DatabaseObject(getObject(pType), getMapping(pType)); + } + + /** + * Reads the object of the given type and with the given id from the + * database + */ + + // interface + public Object getObject(String pType, Object pId) + throws SQLException { + + // Create DatabaseReadable and set id + DatabaseObject dbObject = (DatabaseObject) getDatabaseReadable(pType); + dbObject.setId(pId); + + // Read it + dbObject = (DatabaseObject) mObjectReader.readObject(dbObject); + + // Return it + return dbObject.getObject(); + } + + /** + * Reads the objects of the given type and with the given ids from the + * database + */ + + // interface + public Object[] getObjects(String pType, Object[] pIds) + throws SQLException { + + // Create Vector to hold the result + Vector result = new Vector(pIds.length); + + // Loop through Id's and fetch one at a time (no good performance...) + for (int i = 0; i < pIds.length; i++) { + // Create DBObject, set id and read it + DatabaseObject dbObject = + (DatabaseObject) getDatabaseReadable(pType); + + dbObject.setId(pIds[i]); + dbObject = (DatabaseObject) mObjectReader.readObject(dbObject); + + // Add to result if not null + if (dbObject != null) { + result.add(dbObject.getObject()); + } + } + + // Create array of correct type, length equal to Vector + Class cl = getType(pType); + Object[] arr = (Object[]) Array.newInstance(cl, result.size()); + + // Return the vector as an array + return result.toArray(arr); + } + + /** + * Reads the objects of the given type and with the given properties from + * the database + */ + + // interface + public Object[] getObjects(String pType, Hashtable pWhere) + throws SQLException { + return mObjectReader.readObjects(getDatabaseReadable(pType), pWhere); + } + + /** + * Reads all objects of the given type from the database + */ + + // interface + public Object[] getObjects(String pType) + throws SQLException { + return mObjectReader.readObjects(getDatabaseReadable(pType)); + } + + // interface + public Object addObject(Object pObject) { + // get id... + + return pObject; + } + + // interface + public Object updateObject(Object pObject) { + // get id... + + return pObject; + } + + // interface + public abstract Object deleteObject(String pType, Object pId); + + // interface + public abstract Object deleteObject(Object pObject); + + // interface + public abstract Object createObject(String pType, Object pId); + + // interface + public abstract Object createObject(String pType); + +} + +/** + * Utility class for reading Objects from the database + */ + +class DatabaseObject implements DatabaseReadable { + Hashtable mMapping = null; + + Object mId = null; + Object mObject = null; + + public DatabaseObject(Object pObject, Hashtable pMapping) { + setObject(pObject); + setMapping(pMapping); + } + + public Object getId() { + return mId; + } + + public void setId(Object pId) { + mId = pId; + } + + public void setObject(Object pObject) { + mObject = pObject; + } + public Object getObject() { + return mObject; + } + + public void setMapping(Hashtable pMapping) { + mMapping = pMapping; + } + + public Hashtable getMapping() { + return mMapping; + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java new file mode 100755 index 00000000..a5922979 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java @@ -0,0 +1,663 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + +import com.twelvemonkeys.lang.*; + +import java.lang.reflect.*; + +// Single-type import, to avoid util.Date/sql.Date confusion +import java.util.Hashtable; +import java.util.Vector; +import java.util.Enumeration; +import java.util.StringTokenizer; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; + +/** + * A class for mapping JDBC ResultSet rows to Java objects. + * + * @see ObjectReader + * + * @author Harald Kuhr (haraldk@iconmedialab.no) + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java#1 $ + * + * @todo Use JDK logging instead of proprietary logging. + */ +public class ObjectMapper { + final static String DIRECTMAP = "direct"; + final static String OBJECTMAP = "object"; + final static String COLLECTIONMAP = "collection"; + final static String OBJCOLLMAP = "objectcollection"; + + Class mInstanceClass = null; + + Hashtable mMethods = null; + + Hashtable mColumnMap = null; + Hashtable mPropertiesMap = null; + + Hashtable mJoins = null; + + private Hashtable mTables = null; + private Vector mColumns = null; + + Hashtable mForeignKeys = null; + Hashtable mPrimaryKeys = null; + Hashtable mMapTypes = null; + Hashtable mClasses = null; + + String mPrimaryKey = null; + String mForeignKey = null; + String mIdentityJoin = null; + + Log mLog = null; + + /** + * Creates a new ObjectMapper for a DatabaseReadable + * + * @param obj An object of type DatabaseReadable + */ + + /* + public ObjectMapper(DatabaseReadable obj) { + this(obj.getClass(), obj.getMapping()); + } + */ + /** + * Creates a new ObjectMapper for any object, given a mapping + * + * @param objClass The class of the object(s) created by this OM + * @param mapping an Hashtable containing the mapping information + * for this OM + */ + + public ObjectMapper(Class pObjClass, Hashtable pMapping) { + mLog = new Log(this); + + mInstanceClass = pObjClass; + + mJoins = new Hashtable(); + mPropertiesMap = new Hashtable(); + mColumnMap = new Hashtable(); + + mClasses = new Hashtable(); + mMapTypes = new Hashtable(); + mForeignKeys = new Hashtable(); + mPrimaryKeys = new Hashtable(); + + // Unpack and store mapping information + for (Enumeration keys = pMapping.keys(); keys.hasMoreElements();) { + String key = (String) keys.nextElement(); + String value = (String) pMapping.get(key); + + int dotIdx = key.indexOf("."); + + if (dotIdx >= 0) { + if (key.equals(".primaryKey")) { + // Primary key + mPrimaryKey = (String) pMapping.get(value); + } + else if (key.equals(".foreignKey")) { + // Foreign key + mForeignKey = (String) pMapping.get(value); + } + else if (key.equals(".join")) { + // Identity join + mIdentityJoin = (String) pMapping.get(key); + } + else if (key.endsWith(".primaryKey")) { + // Primary key in joining table + mPrimaryKeys.put(key.substring(0, dotIdx), value); + } + else if (key.endsWith(".foreignKey")) { + // Foreign key + mForeignKeys.put(key.substring(0, dotIdx), value); + } + else if (key.endsWith(".join")) { + // Joins + mJoins.put(key.substring(0, dotIdx), value); + } + else if (key.endsWith(".mapType")) { + // Maptypes + value = value.toLowerCase(); + + if (value.equals(DIRECTMAP) || value.equals(OBJECTMAP) || + value.equals(COLLECTIONMAP) || + value.equals(OBJCOLLMAP)) { + mMapTypes.put(key.substring(0, dotIdx), value); + } + else { + mLog.logError("Illegal mapType: \"" + value + "\"! " + + "Legal types are: direct, object, " + + "collection and objectCollection."); + } + } + else if (key.endsWith(".class")) { + // Classes + try { + mClasses.put(key.substring(0, dotIdx), + Class.forName(value)); + } + catch (ClassNotFoundException e) { + mLog.logError(e); + //e.printStackTrace(); + } + } + else if (key.endsWith(".collection")) { + // TODO!! + } + } + else { + // Property to column mappings + mPropertiesMap.put(key, value); + mColumnMap.put(value.substring(value.lastIndexOf(".") + 1), + key); + } + } + + mMethods = new Hashtable(); + Method[] methods = mInstanceClass.getMethods(); + for (int i = 0; i < methods.length; i++) { + // Two methods CAN have same name... + mMethods.put(methods[i].getName(), methods[i]); + } + } + + public void setPrimaryKey(String pPrimaryKey) { + mPrimaryKey = pPrimaryKey; + } + + /** + * Gets the name of the property, that acts as the unique identifier for + * this ObjectMappers type. + * + * @return The name of the primary key property + */ + + public String getPrimaryKey() { + return mPrimaryKey; + } + + public String getForeignKey() { + return mForeignKey; + } + + /** + * Gets the join, that is needed to find this ObjectMappers type. + * + * @return The name of the primary key property + */ + + public String getIdentityJoin() { + return mIdentityJoin; + } + + Hashtable getPropertyMapping(String pProperty) { + Hashtable mapping = new Hashtable(); + + if (pProperty != null) { + // Property + if (mPropertiesMap.containsKey(pProperty)) + mapping.put("object", mPropertiesMap.get(pProperty)); + + // Primary key + if (mPrimaryKeys.containsKey(pProperty)) { + mapping.put(".primaryKey", "id"); + mapping.put("id", mPrimaryKeys.get(pProperty)); + } + + //Foreign key + if (mForeignKeys.containsKey(pProperty)) + mapping.put(".foreignKey", mPropertiesMap.get(mForeignKeys.get(pProperty))); + + // Join + if (mJoins.containsKey(pProperty)) + mapping.put(".join", mJoins.get(pProperty)); + + // mapType + mapping.put(".mapType", "object"); + } + + return mapping; + } + + + /** + * Gets the column for a given property. + * + * @param property The property + * @return The name of the matching database column, on the form + * table.column + */ + + public String getColumn(String pProperty) { + if (mPropertiesMap == null || pProperty == null) + return null; + return (String) mPropertiesMap.get(pProperty); + } + + /** + * Gets the table name for a given property. + * + * @param property The property + * @return The name of the matching database table. + */ + + public String getTable(String pProperty) { + String table = getColumn(pProperty); + + if (table != null) { + int dotIdx = 0; + if ((dotIdx = table.lastIndexOf(".")) >= 0) + table = table.substring(0, dotIdx); + else + return null; + } + + return table; + } + + /** + * Gets the property for a given database column. If the column incudes + * table qualifier, the table qualifier is removed. + * + * @param column The name of the column + * @return The name of the mathcing property + */ + + public String getProperty(String pColumn) { + if (mColumnMap == null || pColumn == null) + return null; + + String property = (String) mColumnMap.get(pColumn); + + int dotIdx = 0; + if (property == null && (dotIdx = pColumn.lastIndexOf(".")) >= 0) + property = (String) mColumnMap.get(pColumn.substring(dotIdx + 1)); + + return property; + } + + + /** + * Maps each row of the given result set to an object ot this OM's type. + * + * @param rs The ResultSet to process (map to objects) + * @return An array of objects (of this OM's class). If there are no rows + * in the ResultSet, an empty (zero-length) array will be returned. + */ + + public synchronized Object[] mapObjects(ResultSet pRSet) throws SQLException { + Vector result = new Vector(); + + ResultSetMetaData meta = pRSet.getMetaData(); + int cols = meta.getColumnCount(); + + // Get colum names + String[] colNames = new String[cols]; + for (int i = 0; i < cols; i++) { + colNames[i] = meta.getColumnName(i + 1); // JDBC cols start at 1... + + /* + System.out.println(meta.getColumnLabel(i + 1)); + System.out.println(meta.getColumnName(i + 1)); + System.out.println(meta.getColumnType(i + 1)); + System.out.println(meta.getColumnTypeName(i + 1)); + // System.out.println(meta.getTableName(i + 1)); + // System.out.println(meta.getCatalogName(i + 1)); + // System.out.println(meta.getSchemaName(i + 1)); + // Last three NOT IMPLEMENTED!! + */ + } + + // Loop through rows in resultset + while (pRSet.next()) { + Object obj = null; + + try { + obj = mInstanceClass.newInstance(); // Asserts empty constructor! + } + catch (IllegalAccessException iae) { + mLog.logError(iae); + // iae.printStackTrace(); + } + catch (InstantiationException ie) { + mLog.logError(ie); + // ie.printStackTrace(); + } + + // Read each colum from this row into object + for (int i = 0; i < cols; i++) { + + String property = (String) mColumnMap.get(colNames[i]); + + if (property != null) { + // This column is mapped to a property + mapColumnProperty(pRSet, i + 1, property, obj); + } + } + + // Add object to the result Vector + result.addElement(obj); + } + + return result.toArray((Object[]) Array.newInstance(mInstanceClass, + result.size())); + } + + /** + * Maps a ResultSet column (from the current ResultSet row) to a named + * property of an object, using reflection. + * + * @param rs The JDBC ResultSet + * @param index The column index to get the value from + * @param property The name of the property to set the value of + * @param obj The object to set the property to + */ + + void mapColumnProperty(ResultSet pRSet, int pIndex, String pProperty, + Object pObj) { + if (pRSet == null || pProperty == null || pObj == null) + throw new IllegalArgumentException("ResultSet, Property or Object" + + " arguments cannot be null!"); + if (pIndex <= 0) + throw new IllegalArgumentException("Index parameter must be > 0!"); + + String methodName = "set" + StringUtil.capitalize(pProperty); + Method setMethod = (Method) mMethods.get(methodName); + + if (setMethod == null) { + // No setMethod for this property + mLog.logError("No set method for property \"" + + pProperty + "\" in " + pObj.getClass() + "!"); + return; + } + + // System.err.println("DEBUG: setMethod=" + setMethod); + + Method getMethod = null; + + String type = ""; + try { + Class[] cl = {Integer.TYPE}; + type = setMethod.getParameterTypes()[0].getName(); + + type = type.substring(type.lastIndexOf(".") + 1); + + // There is no getInteger, use getInt instead + if (type.equals("Integer")) { + type = "int"; + } + + // System.err.println("DEBUG: type=" + type); + getMethod = pRSet.getClass(). + getMethod("get" + StringUtil.capitalize(type), cl); + } + catch (Exception e) { + mLog.logError("Can't find method \"get" + + StringUtil.capitalize(type) + "(int)\" " + + "(for class " + StringUtil.capitalize(type) + + ") in ResultSet", e); + + return; + } + + try { + // Get the data from the DB + // System.err.println("DEBUG: " + getMethod.getName() + "(" + (i + 1) + ")"); + + Object[] colIdx = {new Integer(pIndex)}; + Object[] arg = {getMethod.invoke(pRSet, colIdx)}; + + // Set it to the object + // System.err.println("DEBUG: " + setMethod.getName() + "(" + arg[0] + ")"); + setMethod.invoke(pObj, arg); + } + catch (InvocationTargetException ite) { + mLog.logError(ite); + // ite.printStackTrace(); + } + catch (IllegalAccessException iae) { + mLog.logError(iae); + // iae.printStackTrace(); + } + } + + /** + * Creates a SQL query string to get the primary keys for this + * ObjectMapper. + */ + + String buildIdentitySQL(String[] pKeys) { + mTables = new Hashtable(); + mColumns = new Vector(); + + // Get columns to select + mColumns.addElement(getPrimaryKey()); + + // Get tables to select (and join) from and their joins + tableJoins(null, false); + + for (int i = 0; i < pKeys.length; i++) { + tableJoins(getColumn(pKeys[i]), true); + } + + // All data read, build SQL query string + return "SELECT " + getPrimaryKey() + " " + buildFromClause() + + buildWhereClause(); + } + + /** + * Creates a SQL query string to get objects for this ObjectMapper. + */ + + public String buildSQL() { + mTables = new Hashtable(); + mColumns = new Vector(); + + String key = null; + for (Enumeration keys = mPropertiesMap.keys(); keys.hasMoreElements();) { + key = (String) keys.nextElement(); + + // Get columns to select + String column = (String) mPropertiesMap.get(key); + mColumns.addElement(column); + + tableJoins(column, false); + } + + // All data read, build SQL query string + return buildSelectClause() + buildFromClause() + + buildWhereClause(); + } + + /** + * Builds a SQL SELECT clause from the columns Vector + */ + + private String buildSelectClause() { + StringBuilder sqlBuf = new StringBuilder(); + + sqlBuf.append("SELECT "); + + String column = null; + for (Enumeration select = mColumns.elements(); select.hasMoreElements();) { + column = (String) select.nextElement(); + + /* + String subColumn = column.substring(column.indexOf(".") + 1); + // System.err.println("DEBUG: col=" + subColumn); + String mapType = (String) mMapTypes.get(mColumnMap.get(subColumn)); + */ + String mapType = (String) mMapTypes.get(getProperty(column)); + + if (mapType == null || mapType.equals(DIRECTMAP)) { + sqlBuf.append(column); + + sqlBuf.append(select.hasMoreElements() ? ", " : " "); + } + } + + return sqlBuf.toString(); + } + + /** + * Builds a SQL FROM clause from the tables/joins Hashtable + */ + + private String buildFromClause() { + StringBuilder sqlBuf = new StringBuilder(); + + sqlBuf.append("FROM "); + + String table = null; + String schema = null; + for (Enumeration from = mTables.keys(); from.hasMoreElements();) { + table = (String) from.nextElement(); + /* + schema = (String) schemas.get(table); + + if (schema != null) + sqlBuf.append(schema + "."); + */ + + sqlBuf.append(table); + sqlBuf.append(from.hasMoreElements() ? ", " : " "); + } + + return sqlBuf.toString(); + } + + /** + * Builds a SQL WHERE clause from the tables/joins Hashtable + * + * @return Currently, this metod will return "WHERE 1 = 1", if no other + * WHERE conditions are specified. This can be considered a hack. + */ + + private String buildWhereClause() { + + StringBuilder sqlBuf = new StringBuilder(); + + String join = null; + boolean first = true; + + for (Enumeration where = mTables.elements(); where.hasMoreElements();) { + join = (String) where.nextElement(); + + if (join.length() > 0) { + if (first) { + // Skip " AND " in first iteration + first = false; + } + else { + sqlBuf.append(" AND "); + } + } + + sqlBuf.append(join); + } + + if (sqlBuf.length() > 0) + return "WHERE " + sqlBuf.toString(); + + return "WHERE 1 = 1"; // Hacky... + } + + /** + * Finds tables used in mappings and joins and adds them to the tables + * Hashtable, with the table name as key, and the join as value. + */ + + private void tableJoins(String pColumn, boolean pWhereJoin) { + String join = null; + String table = null; + + if (pColumn == null) { + // Identity + join = getIdentityJoin(); + table = getTable(getProperty(getPrimaryKey())); + } + else { + // Normal + int dotIndex = -1; + if ((dotIndex = pColumn.lastIndexOf(".")) <= 0) { + // No table qualifier + return; + } + + // Get table qualifier. + table = pColumn.substring(0, dotIndex); + + // Don't care about the tables that are not supposed to be selected from + String property = (String) getProperty(pColumn); + + if (property != null) { + String mapType = (String) mMapTypes.get(property); + if (!pWhereJoin && mapType != null && !mapType.equals(DIRECTMAP)) { + return; + } + + join = (String) mJoins.get(property); + } + } + + // If table is not in the tables Hash, add it, and check for joins. + if (mTables.get(table) == null) { + if (join != null) { + mTables.put(table, join); + + StringTokenizer tok = new StringTokenizer(join, "= "); + String next = null; + + while(tok.hasMoreElements()) { + next = tok.nextToken(); + // Don't care about SQL keywords + if (next.equals("AND") || next.equals("OR") + || next.equals("NOT") || next.equals("IN")) { + continue; + } + // Check for new tables and joins in this join clause. + tableJoins(next, false); + } + } + else { + // No joins for this table. + join = ""; + mTables.put(table, join); + } + } + } + +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectReader.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectReader.java new file mode 100755 index 00000000..ffaf8a16 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectReader.java @@ -0,0 +1,879 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + + +import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.lang.SystemUtil; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; + +/** + * Class used for reading table data from a database through JDBC, and map + * the data to Java classes. + * + * @see ObjectMapper + * + * @author Harald Kuhr (haraldk@iconmedialab.no) + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sql/ObjectReader.java#1 $ + * + * @todo Use JDK logging instead of proprietary logging. + * + */ +public class ObjectReader { + + /** + * Main method, for testing purposes only. + */ + + public final static void main(String[] pArgs) throws SQLException { + /* + System.err.println("Testing only!"); + + // Get default connection + ObjectReader obr = new ObjectReader(DatabaseConnection.getConnection()); + + com.twelvemonkeys.usedcars.DBCar car = new com.twelvemonkeys.usedcars.DBCar(new Integer(1)); + com.twelvemonkeys.usedcars.DBDealer dealer = new com.twelvemonkeys.usedcars.DBDealer("NO4537"); + + System.out.println(obr.readObject(dealer)); + com.twelvemonkeys.usedcars.Dealer[] dealers = (com.twelvemonkeys.usedcars.Dealer[]) obr.readObjects(dealer); + + for (int i = 0; i < dealers.length; i++) { + System.out.println(dealers[i]); + } + + System.out.println("------------------------------------------------------------------------------\n" + + "Total: " + dealers.length + " dealers in database\n"); + + Hashtable where = new Hashtable(); + + where.put("zipCode", "0655"); + dealers = (com.twelvemonkeys.usedcars.Dealer[]) obr.readObjects(dealer, where); + + for (int i = 0; i < dealers.length; i++) { + System.out.println(dealers[i]); + } + + System.out.println("------------------------------------------------------------------------------\n" + + "Total: " + dealers.length + " dealers matching query: " + + where + "\n"); + + + com.twelvemonkeys.usedcars.Car[] cars = null; + cars = (com.twelvemonkeys.usedcars.Car[]) obr.readObjects(car); + + for (int i = 0; i < cars.length; i++) { + System.out.println(cars[i]); + } + + System.out.println("------------------------------------------------------------------------------\n" + + "Total: " + cars.length + " cars in database\n"); + + where = new Hashtable(); + where.put("year", new Integer(1995)); + cars = (com.twelvemonkeys.usedcars.Car[]) obr.readObjects(car, where); + + for (int i = 0; i < cars.length; i++) { + System.out.println(cars[i]); + } + + System.out.println("------------------------------------------------------------------------------\n" + + "Total: " + cars.length + " cars matching query: " + + where + " \n"); + + + where = new Hashtable(); + where.put("publishers", "Bilguiden"); + cars = (com.twelvemonkeys.usedcars.Car[]) obr.readObjects(car, where); + + for (int i = 0; i < cars.length; i++) { + System.out.println(cars[i]); + } + + System.out.println("------------------------------------------------------------------------------\n" + + "Total: " + cars.length + " cars matching query: " + + where + "\n"); + + System.out.println("==============================================================================\n" + + getStats()); + */ + } + + + protected Log mLog = null; + protected Properties mConfig = null; + + /** + * The connection used for all database operations executed by this + * ObjectReader. + */ + + Connection mConnection = null; + + /** + * The cache for this ObjectReader. + * Probably a source for memory leaks, as it has no size limitations. + */ + + private Hashtable mCache = new Hashtable(); + + /** + * Creates a new ObjectReader, using the given JDBC Connection. The + * Connection will be used for all database reads by this ObjectReader. + * + * @param connection A JDBC Connection + */ + + public ObjectReader(Connection pConnection) { + mConnection = pConnection; + + try { + mConfig = SystemUtil.loadProperties(getClass()); + } + catch (FileNotFoundException fnf) { + // Just go with defaults + } + catch (IOException ioe) { + new Log(this).logError(ioe); + } + + mLog = new Log(this, mConfig); + } + + /** + * Gets a string containing the stats for this ObjectReader. + * + * @return A string to display the stats. + */ + + public static String getStats() { + long total = sCacheHit + sCacheMiss + sCacheUn; + double hit = ((double) sCacheHit / (double) total) * 100.0; + double miss = ((double) sCacheMiss / (double) total) * 100.0; + double un = ((double) sCacheUn / (double) total) * 100.0; + + // Default locale + java.text.NumberFormat nf = java.text.NumberFormat.getInstance(); + + return "Total: " + total + " reads. " + + "Cache hits: " + sCacheHit + " (" + nf.format(hit) + "%), " + + "Cache misses: " + sCacheMiss + " (" + nf.format(miss) + "%), " + + "Unattempted: " + sCacheUn + " (" + nf.format(un) + "%) "; + } + + /** + * Get an array containing Objects of type objClass, with the + * identity values for the given class set. + */ + + private Object[] readIdentities(Class pObjClass, Hashtable pMapping, + Hashtable pWhere, ObjectMapper pOM) + throws SQLException { + sCacheUn++; + // Build SQL query string + if (pWhere == null) + pWhere = new Hashtable(); + + String[] keys = new String[pWhere.size()]; + int i = 0; + for (Enumeration en = pWhere.keys(); en.hasMoreElements(); i++) { + keys[i] = (String) en.nextElement(); + } + + // Get SQL for reading identity column + String sql = pOM.buildIdentitySQL(keys) + + buildWhereClause(keys, pMapping); + + // Log? + mLog.logDebug(sql + " (" + pWhere + ")"); + + // Prepare statement and set values + PreparedStatement statement = mConnection.prepareStatement(sql); + for (int j = 0; j < keys.length; j++) { + Object key = pWhere.get(keys[j]); + + if (key instanceof Integer) + statement.setInt(j + 1, ((Integer) key).intValue()); + else if (key instanceof BigDecimal) + statement.setBigDecimal(j + 1, (BigDecimal) key); + else + statement.setString(j + 1, key.toString()); + } + + // Execute query + ResultSet rs = null; + try { + rs = statement.executeQuery(); + } + catch (SQLException e) { + mLog.logError(sql + " (" + pWhere + ")", e); + throw e; + } + Vector result = new Vector(); + + // Map query to objects + while (rs.next()) { + Object obj = null; + + try { + obj = pObjClass.newInstance(); + } + catch (IllegalAccessException iae) { + iae.printStackTrace(); + } + catch (InstantiationException ie) { + ie.printStackTrace(); + } + + // Map it + pOM.mapColumnProperty(rs, 1, + pOM.getProperty(pOM.getPrimaryKey()), obj); + result.addElement(obj); + } + + // Return array of identifiers + return result.toArray((Object[]) Array.newInstance(pObjClass, + result.size())); + } + + + /** + * Reads one object implementing the DatabaseReadable interface from the + * database. + * + * @param readable A DatabaseReadable object + * @return The Object read, or null in no object is found + */ + + public Object readObject(DatabaseReadable pReadable) throws SQLException { + return readObject(pReadable.getId(), pReadable.getClass(), + pReadable.getMapping()); + } + + /** + * Reads the object with the given id from the database, using the given + * mapping. + * + * @param id An object uniquely identifying the object to read + * @param objClass The clas + * @return The Object read, or null in no object is found + */ + + public Object readObject(Object pId, Class pObjClass, Hashtable pMapping) + throws SQLException { + return readObject(pId, pObjClass, pMapping, null); + } + + /** + * Reads all the objects of the given type from the + * database. The object must implement the DatabaseReadable interface. + * + * @return An array of Objects, or an zero-length array if none was found + */ + + public Object[] readObjects(DatabaseReadable pReadable) + throws SQLException { + return readObjects(pReadable.getClass(), + pReadable.getMapping(), null); + } + + /** + * Sets the property value to an object using reflection + * + * @param obj The object to get a property from + * @param property The name of the property + * @param value The property value + * + */ + + private void setPropertyValue(Object pObj, String pProperty, + Object pValue) { + + Method m = null; + Class[] cl = {pValue.getClass()}; + + try { + //Util.setPropertyValue(pObj, pProperty, pValue); + + // Find method + m = pObj.getClass(). + getMethod("set" + StringUtil.capitalize(pProperty), cl); + // Invoke it + Object[] args = {pValue}; + m.invoke(pObj, args); + + } + catch (NoSuchMethodException e) { + e.printStackTrace(); + } + catch (IllegalAccessException iae) { + iae.printStackTrace(); + } + catch (InvocationTargetException ite) { + ite.printStackTrace(); + } + + } + + /** + * Gets the property value from an object using reflection + * + * @param obj The object to get a property from + * @param property The name of the property + * + * @return The property value as an Object + */ + + private Object getPropertyValue(Object pObj, String pProperty) { + + Method m = null; + Class[] cl = new Class[0]; + + try { + //return Util.getPropertyValue(pObj, pProperty); + + // Find method + m = pObj.getClass(). + getMethod("get" + StringUtil.capitalize(pProperty), + new Class[0]); + // Invoke it + Object result = m.invoke(pObj, new Object[0]); + return result; + + } + catch (NoSuchMethodException e) { + e.printStackTrace(); + } + catch (IllegalAccessException iae) { + iae.printStackTrace(); + } + catch (InvocationTargetException ite) { + ite.printStackTrace(); + } + return null; + } + + /** + * Reads and sets the child properties of the given parent object. + * + * @param parent The object to set the child obects to. + * @param om The ObjectMapper of the parent object. + */ + + private void setChildObjects(Object pParent, ObjectMapper pOM) + throws SQLException { + if (pOM == null) { + throw new NullPointerException("ObjectMapper in readChildObjects " + + "cannot be null!!"); + } + + for (Enumeration keys = pOM.mMapTypes.keys(); keys.hasMoreElements();) { + String property = (String) keys.nextElement(); + String mapType = (String) pOM.mMapTypes.get(property); + + if (property.length() <= 0 || mapType == null) { + continue; + } + + // Get the id of the parent + Object id = getPropertyValue(pParent, + pOM.getProperty(pOM.getPrimaryKey())); + + if (mapType.equals(ObjectMapper.OBJECTMAP)) { + // OBJECT Mapping + + // Get the class for this property + Class objectClass = (Class) pOM.mClasses.get(property); + + DatabaseReadable dbr = null; + try { + dbr = (DatabaseReadable) objectClass.newInstance(); + } + catch (Exception e) { + mLog.logError(e); + } + + /* + Properties mapping = readMapping(objectClass); + */ + + // Get property mapping for child object + if (pOM.mJoins.containsKey(property)) + // mapping.setProperty(".join", (String) pOM.joins.get(property)); + dbr.getMapping().put(".join", pOM.mJoins.get(property)); + + // Find id and put in where hash + Hashtable where = new Hashtable(); + + // String foreignKey = mapping.getProperty(".foreignKey"); + String foreignKey = (String) + dbr.getMapping().get(".foreignKey"); + + if (foreignKey != null) { + where.put(".foreignKey", id); + } + + Object[] child = readObjects(dbr, where); + // Object[] child = readObjects(objectClass, mapping, where); + + if (child.length < 1) + throw new SQLException("No child object with foreign key " + + foreignKey + "=" + id); + else if (child.length != 1) + throw new SQLException("More than one object with foreign " + + "key " + foreignKey + "=" + id); + + // Set child object to the parent + setPropertyValue(pParent, property, child[0]); + } + else if (mapType.equals(ObjectMapper.COLLECTIONMAP)) { + // COLLECTION Mapping + + // Get property mapping for child object + Hashtable mapping = pOM.getPropertyMapping(property); + + // Find id and put in where hash + Hashtable where = new Hashtable(); + String foreignKey = (String) mapping.get(".foreignKey"); + if (foreignKey != null) { + where.put(".foreignKey", id); + } + + DBObject dbr = new DBObject(); + dbr.mapping = mapping; // ugh... + // Read the objects + Object[] objs = readObjects(dbr, where); + + // Put the objects in a hash + Hashtable children = new Hashtable(); + for (int i = 0; i < objs.length; i++) { + children.put(((DBObject) objs[i]).getId(), + ((DBObject) objs[i]).getObject()); + } + + // Set child properties to parent object + setPropertyValue(pParent, property, children); + } + } + } + + /** + * Reads all objects from the database, using the given mapping. + * + * @param objClass The class of the objects to read + * @param mapping The hashtable containing the object mapping + * + * @return An array of Objects, or an zero-length array if none was found + */ + + public Object[] readObjects(Class pObjClass, Hashtable pMapping) + throws SQLException { + return readObjects(pObjClass, pMapping, null); + } + + /** + * Builds extra SQL WHERE clause + * + * @param keys An array of ID names + * @param mapping The hashtable containing the object mapping + * + * @return A string containing valid SQL + */ + + private String buildWhereClause(String[] pKeys, Hashtable pMapping) { + StringBuilder sqlBuf = new StringBuilder(); + + for (int i = 0; i < pKeys.length; i++) { + String column = (String) pMapping.get(pKeys[i]); + sqlBuf.append(" AND "); + sqlBuf.append(column); + sqlBuf.append(" = ?"); + } + + return sqlBuf.toString(); + + } + + private String buildIdInClause(Object[] pIds, Hashtable pMapping) { + StringBuilder sqlBuf = new StringBuilder(); + + if (pIds != null && pIds.length > 0) { + sqlBuf.append(" AND "); + sqlBuf.append(pMapping.get(".primaryKey")); + sqlBuf.append(" IN ("); + + for (int i = 0; i < pIds.length; i++) { + sqlBuf.append(pIds[i]); // SETTE INN '?' ??? + sqlBuf.append(", "); + } + sqlBuf.append(")"); + } + + return sqlBuf.toString(); + + } + + /** + * Reads all objects from the database, using the given mapping. + * + * @param readable A DatabaseReadable object + * @param mapping The hashtable containing the object mapping + * + * @return An array of Objects, or an zero-length array if none was found + */ + + public Object[] readObjects(DatabaseReadable pReadable, Hashtable pWhere) + throws SQLException { + return readObjects(pReadable.getClass(), + pReadable.getMapping(), pWhere); + } + + + /** + * Reads the object with the given id from the database, using the given + * mapping. + * This is the most general form of readObject(). + * + * @param id An object uniquely identifying the object to read + * @param objClass The class of the object to read + * @param mapping The hashtable containing the object mapping + * @param where An hashtable containing extra criteria for the read + * + * @return An array of Objects, or an zero-length array if none was found + */ + + public Object readObject(Object pId, Class pObjClass, + Hashtable pMapping, Hashtable pWhere) + throws SQLException { + ObjectMapper om = new ObjectMapper(pObjClass, pMapping); + return readObject0(pId, pObjClass, om, pWhere); + } + + public Object readObjects(Object[] pIds, Class pObjClass, + Hashtable pMapping, Hashtable pWhere) + throws SQLException { + ObjectMapper om = new ObjectMapper(pObjClass, pMapping); + return readObjects0(pIds, pObjClass, om, pWhere); + } + + /** + * Reads all objects from the database, using the given mapping. + * This is the most general form of readObjects(). + * + * @param objClass The class of the objects to read + * @param mapping The hashtable containing the object mapping + * @param where An hashtable containing extra criteria for the read + * + * @return An array of Objects, or an zero-length array if none was found + */ + + public Object[] readObjects(Class pObjClass, Hashtable pMapping, + Hashtable pWhere) throws SQLException { + return readObjects0(pObjClass, pMapping, pWhere); + } + + // readObjects implementation + + private Object[] readObjects0(Class pObjClass, Hashtable pMapping, + Hashtable pWhere) throws SQLException { + ObjectMapper om = new ObjectMapper(pObjClass, pMapping); + + Object[] ids = readIdentities(pObjClass, pMapping, pWhere, om); + + Object[] result = readObjects0(ids, pObjClass, om, pWhere); + + return result; + } + + private Object[] readObjects0(Object[] pIds, Class pObjClass, + ObjectMapper pOM, Hashtable pWhere) + throws SQLException { + Object[] result = new Object[pIds.length]; + + // Read each object from ID + for (int i = 0; i < pIds.length; i++) { + + // TODO: For better cahce efficiency/performance: + // - Read as many objects from cache as possible + // - Read all others in ONE query, and add to cache + /* + sCacheUn++; + // Build SQL query string + if (pWhere == null) + pWhere = new Hashtable(); + + String[] keys = new String[pWhere.size()]; + int i = 0; + for (Enumeration en = pWhere.keys(); en.hasMoreElements(); i++) { + keys[i] = (String) en.nextElement(); + } + + // Get SQL for reading identity column + String sql = pOM.buildSelectClause() + pOM.buildFromClause() + + + buildWhereClause(keys, pMapping) + buildIdInClause(pIds, pMapping); + + // Log? + mLog.logDebug(sql + " (" + pWhere + ")"); + + + // Log? + mLog.logDebug(sql + " (" + pWhere + ")"); + + PreparedStatement statement = null; + + // Execute query, and map columns/properties + try { + statement = mConnection.prepareStatement(sql); + + // Set keys + for (int j = 0; j < keys.length; j++) { + Object value = pWhere.get(keys[j]); + + if (value instanceof Integer) + statement.setInt(j + 1, ((Integer) value).intValue()); + else if (value instanceof BigDecimal) + statement.setBigDecimal(j + 1, (BigDecimal) value); + else + statement.setString(j + 1, value.toString()); + } + // Set ids + for (int j = 0; j < pIds.length; j++) { + Object id = pIds[i]; + + if (id instanceof Integer) + statement.setInt(j + 1, ((Integer) id).intValue()); + else if (id instanceof BigDecimal) + statement.setBigDecimal(j + 1, (BigDecimal) id); + else + statement.setString(j + 1, id.toString()); + } + + ResultSet rs = statement.executeQuery(); + + Object[] result = pOM.mapObjects(rs); + + // Set child objects and return + for (int i = 0; i < result.length; i++) { + // FOR THIS TO REALLY GET EFFECTIVE, WE NEED TO SET ALL + // CHILDREN IN ONE GO! + setChildObjects(result[i], pOM); + mContent.put(pOM.getPrimaryKey() + "=" + pId, result[0]); + + } + // Return result + return result[0]; + + } + */ + + Object id = getPropertyValue(result[i], + pOM.getProperty(pOM.getPrimaryKey())); + + result[i] = readObject0(id, pObjClass, pOM, null); + + } + + return result; + } + + // readObject implementation, used for ALL database reads + + static long sCacheHit; + static long sCacheMiss; + static long sCacheUn; + + private Object readObject0(Object pId, Class pObjClass, ObjectMapper pOM, + Hashtable pWhere) throws SQLException { + if (pId == null && pWhere == null) + throw new IllegalArgumentException("Either id or where argument" + + "must be non-null!"); + + // First check if object exists in cache + if (pId != null) { + Object o = mCache.get(pOM.getPrimaryKey() + "=" + pId); + if (o != null) { + sCacheHit++; + return o; + } + sCacheMiss++; + } + else { + sCacheUn++; + } + + // Create where hash + if (pWhere == null) + pWhere = new Hashtable(); + + // Make sure the ID is in the where hash + if (pId != null) + pWhere.put(pOM.getProperty(pOM.getPrimaryKey()), pId); + + String[] keys = new String[pWhere.size()]; + Enumeration en = pWhere.keys(); + for (int i = 0; en.hasMoreElements(); i++) { + keys[i] = (String) en.nextElement(); + } + + // Get SQL query string + String sql = pOM.buildSQL() + buildWhereClause(keys, pOM.mPropertiesMap); + + // Log? + mLog.logDebug(sql + " (" + pWhere + ")"); + + PreparedStatement statement = null; + + // Execute query, and map columns/properties + try { + statement = mConnection.prepareStatement(sql); + + for (int j = 0; j < keys.length; j++) { + Object value = pWhere.get(keys[j]); + + if (value instanceof Integer) + statement.setInt(j + 1, ((Integer) value).intValue()); + else if (value instanceof BigDecimal) + statement.setBigDecimal(j + 1, (BigDecimal) value); + else + statement.setString(j + 1, value.toString()); + } + + ResultSet rs = statement.executeQuery(); + + Object[] result = pOM.mapObjects(rs); + + // Set child objects and return + if (result.length == 1) { + setChildObjects(result[0], pOM); + mCache.put(pOM.getPrimaryKey() + "=" + pId, result[0]); + + // Return result + return result[0]; + } + // More than 1 is an error... + else if (result.length > 1) { + throw new SQLException("More than one object with primary key " + + pOM.getPrimaryKey() + "=" + + pWhere.get(pOM.getProperty(pOM.getPrimaryKey())) + "!"); + } + } + catch (SQLException e) { + mLog.logError(sql + " (" + pWhere + ")", e); + throw e; + } + finally { + try { + statement.close(); + } + catch (SQLException e) { + mLog.logError(e); + } + } + + return null; + } + + /** + * Utility method for reading a property mapping from a properties-file + * + */ + + public static Properties loadMapping(Class pClass) { + try { + return SystemUtil.loadProperties(pClass); + } + catch (FileNotFoundException fnf) { + // System.err... err... + System.err.println("ERROR: " + fnf.getMessage()); + } + catch (IOException ioe) { + ioe.printStackTrace(); + } + return new Properties(); + } + + /** + * @deprecated Use loadMapping(Class) instead + * @see #loadMapping(Class) + */ + + public static Properties readMapping(Class pClass) { + return loadMapping(pClass); + } + + +} + +/** + * Utility class + */ + +class DBObject implements DatabaseReadable { + Object id; + Object o; + static Hashtable mapping; // WHOA, STATIC!?!? + + public DBObject() { + } + + public void setId(Object id) { + this.id = id; + } + public Object getId() { + return id; + } + + public void setObject(Object o) { + this.o = o; + } + public Object getObject() { + return o; + } + + public Hashtable getMapping() { + return mapping; + } +} + + diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/SQLUtil.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/SQLUtil.java new file mode 100755 index 00000000..04052d71 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/SQLUtil.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2008, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.sql; + +import com.twelvemonkeys.lang.StringUtil; + +import java.sql.*; +import java.io.*; +import java.util.Properties; + + +/** + * A class used to test a JDBC database connection. It can also be used as a + * really simple form of command line SQL interface, that passes all command + * line parameters to the database as plain SQL, and returns all rows to + * Sytem.out. Be aware that the wildcard character (*) is intercepted by + * the console, so you have to quote your string, or escape the wildcard + * character, otherwise you may get unpredictable results. + *
+ * Exmaple use + *+ * $ java -cp lib\jconn2.jar;build com.twelvemonkeys.sql.SQLUtil + * -d com.sybase.jdbc2.jdbc.SybDriver -u "jdbc:sybase:Tds:10.248.136.42:6100" + * -l scott -p tiger "SELECT * FROM emp"+ * Make sure sure to include the path to your JDBC driver in the java class + * path! + * + * @author Philippe Béal (phbe@iconmedialab.no) + * @author Harald Kuhr (haraldk@iconmedialab.no) + * @author last modified by $author: WMHAKUR $ + * @version $id: $ + * @see DatabaseConnection + */ +public class SQLUtil { + /** + * Method main + * + * @param pArgs + * @throws SQLException + * + * @todo Refactor the long and ugly main method... + * Consider: - extract method parserArgs(String[])::Properties (how do we + * get the rest of the arguments? getProperty("_ARGV")? + * Make the Properties/Map an argument and return int with last + * option index? + * - extract method getStatementReader(Properties) + */ + public static void main(String[] pArgs) throws SQLException, IOException { + String user = null; + String password = null; + String url = null; + String driver = null; + String configFileName = null; + String scriptFileName = null; + String scriptSQLDelim = "go"; + int argIdx = 0; + boolean errArgs = false; + + while ((argIdx < pArgs.length) && (pArgs[argIdx].charAt(0) == '-') && (pArgs[argIdx].length() >= 2)) { + if ((pArgs[argIdx].charAt(1) == 'l') || pArgs[argIdx].equals("--login")) { + argIdx++; + user = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'p') || pArgs[argIdx].equals("--password")) { + argIdx++; + password = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'u') || pArgs[argIdx].equals("--url")) { + argIdx++; + url = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'd') || pArgs[argIdx].equals("--driver")) { + argIdx++; + driver = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'c') || pArgs[argIdx].equals("--config")) { + argIdx++; + configFileName = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 's') || pArgs[argIdx].equals("--script")) { + argIdx++; + scriptFileName = pArgs[argIdx++]; + } + else if ((pArgs[argIdx].charAt(1) == 'h') || pArgs[argIdx].equals("--help")) { + argIdx++; + errArgs = true; + } + else { + System.err.println("Unknown option \"" + pArgs[argIdx++] + "\""); + } + } + if (errArgs || (scriptFileName == null && (pArgs.length < (argIdx + 1)))) { + System.err.println("Usage: SQLUtil [--help|-h] [--login|-l