From b6ee5ce4502a23a85fc29cdc49b3ac50fa3ea7e3 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 7 Jun 2010 09:55:35 +0200 Subject: [PATCH] Clean-up of sandbox, rearranging everything. Added a couple of files that was never commited. --- .../twelvemonkeys/image/ConvolveTester.java | 0 .../com/twelvemonkeys/image/EasyImage.java | 0 .../image/ExtendedImageConsumer.java | 0 .../image/MappedBufferImage.java | 255 +++++ .../twelvemonkeys/image/SubsampleTester.java | 0 .../java/com/twelvemonkeys/image/inv_cmap.c | 0 .../twelvemonkeys/imageio/LayerSupport.java | 35 + .../com/twelvemonkeys/io/FileLockingTest.java | 64 ++ .../com/twelvemonkeys/io/FileMonitor.java | 57 ++ .../twelvemonkeys/io/enc/DeflateEncoder.java | 0 .../twelvemonkeys/io/enc/InflateDecoder.java | 0 .../com/twelvemonkeys/io/enc/LZWDecoder.java | 0 .../com/twelvemonkeys/io/enc/LZWEncoder.java | 0 .../java/com/twelvemonkeys/lang/DuckType.java | 468 ++++++++++ .../lang/MostUnfortunateException.java | 0 .../com/twelvemonkeys/lang/NativeLoader.java | 0 .../twelvemonkeys/lang/NativeResourceSPI.java | 0 .../twelvemonkeys/sql/DatabaseConnection.java | 247 +++++ .../twelvemonkeys/sql/DatabaseProduct.java | 126 +++ .../twelvemonkeys/sql/DatabaseReadable.java | 70 ++ .../com/twelvemonkeys/sql/JDBCHelper.java | 173 ++++ .../main/java/com/twelvemonkeys/sql/Log.java | 673 ++++++++++++++ .../com/twelvemonkeys/sql/ObjectManager.java | 276 ++++++ .../com/twelvemonkeys/sql/ObjectMapper.java | 663 +++++++++++++ .../com/twelvemonkeys/sql/ObjectReader.java | 879 ++++++++++++++++++ .../java/com/twelvemonkeys/sql/SQLUtil.java | 396 ++++++++ .../java/com/twelvemonkeys/sql/package.html | 12 + .../com/twelvemonkeys/util/DebugUtil.java | 0 .../twelvemonkeys/util/MappedBeanFactory.java | 456 +++++++++ .../com/twelvemonkeys/util/PersistentMap.java | 61 ++ .../com/twelvemonkeys/util/XMLProperties.java | 0 .../util/regex/REWildcardStringParser.java | 0 .../java/com/twelvemonkeys/xml/XMLReader.java | 0 .../io/enc/DeflateEncoderTestCase.java | 0 .../io/enc/InflateDecoderTestCase.java | 0 .../twelvemonkeys/lang/DuckTypeTestCase.java | 246 +++++ .../util/MappedBeanFactoryTestCase.java | 578 ++++++++++++ 37 files changed, 5735 insertions(+) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/image/ConvolveTester.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/image/EasyImage.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java (100%) create mode 100644 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedBufferImage.java rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/image/SubsampleTester.java (100%) rename {common/common-image => sandbox/sandbox-common}/src/main/java/com/twelvemonkeys/image/inv_cmap.c (100%) create mode 100644 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/imageio/LayerSupport.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileLockingTest.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileMonitor.java rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java (100%) create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/DuckType.java rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/lang/NativeLoader.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java (100%) create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseConnection.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseReadable.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/Log.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectManager.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectReader.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/SQLUtil.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/package.html rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/util/DebugUtil.java (100%) create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/MappedBeanFactory.java create mode 100755 sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/util/XMLProperties.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java (100%) rename sandbox/{common => sandbox-common}/src/main/java/com/twelvemonkeys/xml/XMLReader.java (100%) rename sandbox/{common => sandbox-common}/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java (100%) rename sandbox/{common => sandbox-common}/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java (100%) create mode 100755 sandbox/sandbox-common/src/test/java/com/twelvemonkeys/lang/DuckTypeTestCase.java create mode 100755 sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/MappedBeanFactoryTestCase.java 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 ] [--password|-p ] [--driver|-d ] [--url|-u ] [--config|-c ] [--script|-s ] "); + System.exit(5); + } + + // If config file, read config and use as defaults + // NOTE: Command line options override! + if (!StringUtil.isEmpty(configFileName)) { + Properties config = new Properties(); + File configFile = new File(configFileName); + if (!configFile.exists()) { + System.err.println("Config file " + configFile.getAbsolutePath() + " does not exist."); + System.exit(10); + } + + InputStream in = new FileInputStream(configFile); + try { + config.load(in); + } + finally { + in.close(); + } + + if (driver == null) { + driver = config.getProperty("driver"); + } + if (url == null) { + url = config.getProperty("url"); + } + if (user == null) { + user = config.getProperty("login"); + } + if (password == null) { + password = config.getProperty("password"); + } + } + + // Register JDBC driver + if (driver != null) { + registerDriver(driver); + } + Connection conn = null; + + try { + // Use default connection from DatabaseConnection.properties + conn = DatabaseConnection.getConnection(user, password, url); + if (conn == null) { + System.err.println("No connection."); + System.exit(10); + } + + BufferedReader reader; + if (scriptFileName != null) { + // Read SQL from file + File file = new File(scriptFileName); + if (!file.exists()) { + System.err.println("Script file " + file.getAbsolutePath() + " does not exist."); + System.exit(10); + } + + reader = new BufferedReader(new FileReader(file)); + } + else { + // Create SQL statement from command line params + StringBuilder sql = new StringBuilder(); + for (int i = argIdx; i < pArgs.length; i++) { + sql.append(pArgs[i]).append(" "); + } + + reader = new BufferedReader(new StringReader(sql.toString())); + } + + //reader.mark(10000000); + //for (int i = 0; i < 5; i++) { + StringBuilder sql = new StringBuilder(); + while (true) { + // Read next line + String line = reader.readLine(); + if (line == null) { + // End of file, execute and quit + String str = sql.toString(); + if (!StringUtil.isEmpty(str)) { + executeSQL(str, conn); + } + break; + } + else if (line.trim().endsWith(scriptSQLDelim)) { + // End of statement, execute and continue + sql.append(line.substring(0, line.lastIndexOf(scriptSQLDelim))); + executeSQL(sql.toString(), conn); + sql.setLength(0); + } + else { + sql.append(line).append(" "); + } + } + //reader.reset(); + //} + } + finally { + // Close the connection + if (conn != null) { + conn.close(); + } + } + } + + private static void executeSQL(String pSQL, Connection pConn) throws SQLException { + System.out.println("Executing: " + pSQL); + + Statement stmt = null; + try { + // NOTE: Experimental + //stmt = pConn.prepareCall(pSQL); + //boolean results = ((CallableStatement) stmt).execute(); + + // Create statement and execute + stmt = pConn.createStatement(); + boolean results = stmt.execute(pSQL); + + int updateCount = -1; + + SQLWarning warning = stmt.getWarnings(); + while (warning != null) { + System.out.println("Warning: " + warning.getMessage()); + warning = warning.getNextWarning(); + } + + // More result sets to process? + while (results || (updateCount = stmt.getUpdateCount()) != -1) { + // INSERT, UPDATE or DELETE statement (no result set). + if (!results && (updateCount >= 0)) { + System.out.println("Operation successfull. " + updateCount + " row" + ((updateCount != 1) ? "s" : "") + " affected."); + System.out.println(); + } + // SELECT statement or stored procedure + else { + processResultSet(stmt.getResultSet()); + } + + // More results? + results = stmt.getMoreResults(); + } + } + catch (SQLException sqle) { + System.err.println("Error: " + sqle.getMessage()); + while ((sqle = sqle.getNextException()) != null) { + System.err.println(" " + sqle); + } + } + finally { + // Close the statement + if (stmt != null) { + stmt.close(); + } + } + } + + // TODO: Create interface ResultSetProcessor + // -- processWarnings(SQLWarning pWarnings); + // -- processMetaData(ResultSetMetaData pMetas); ?? + // -- processResultSet(ResultSet pResult); + // TODO: Add parameter pResultSetProcessor to method + // TODO: Extract contents of this method to class Default/CLIRSP + // TODO: Create new class JTableRSP that creates (?) and populates a JTable + // or a TableModel (?) + private static void processResultSet(ResultSet pResultSet) throws SQLException { + try { + // Get meta data + ResultSetMetaData meta = pResultSet.getMetaData(); + + // Print any warnings that might have occured + SQLWarning warning = pResultSet.getWarnings(); + while (warning != null) { + System.out.println("Warning: " + warning.getMessage()); + warning = warning.getNextWarning(); + } + + // Get the number of columns in the result set + int numCols = meta.getColumnCount(); + + for (int i = 1; i <= numCols; i++) { + boolean prepend = isNumeric(meta.getColumnType(i)); + + String label = maybePad(meta.getColumnLabel(i), meta.getColumnDisplaySize(i), " ", prepend); + + System.out.print(label + "\t"); + } + System.out.println(); + for (int i = 1; i <= numCols; i++) { + boolean prepend = isNumeric(meta.getColumnType(i)); + String label = maybePad("(" + meta.getColumnTypeName(i) + "/" + meta.getColumnClassName(i) + ")", meta.getColumnDisplaySize(i), " ", prepend); + System.out.print(label + "\t"); + } + System.out.println(); + for (int i = 1; i <= numCols; i++) { + String label = maybePad("", meta.getColumnDisplaySize(i), "-", false); + System.out.print(label + "\t"); + } + System.out.println(); + while (pResultSet.next()) { + for (int i = 1; i <= numCols; i++) { + boolean prepend = isNumeric(meta.getColumnType(i)); + String value = maybePad(String.valueOf(pResultSet.getString(i)), meta.getColumnDisplaySize(i), " ", prepend); + System.out.print(value + "\t"); + //System.out.print(pResultSet.getString(i) + "\t"); + } + System.out.println(); + } + System.out.println(); + } + catch (SQLException sqle) { + System.err.println("Error: " + sqle.getMessage()); + while ((sqle = sqle.getNextException()) != null) { + System.err.println(" " + sqle); + } + throw sqle; + } + finally { + if (pResultSet != null) { + pResultSet.close(); + } + } + } + + private static String maybePad(String pString, int pColumnDisplaySize, String pPad, boolean pPrepend) { + String padded; + if (pColumnDisplaySize < 100) { + padded = StringUtil.pad(pString, pColumnDisplaySize, pPad, pPrepend); + } + else { + padded = StringUtil.pad(pString, 100, pPad, pPrepend); + } + return padded; + } + + private static boolean isNumeric(int pColumnType) { + return (pColumnType == Types.INTEGER || pColumnType == Types.DECIMAL + || pColumnType == Types.TINYINT || pColumnType == Types.BIGINT + || pColumnType == Types.DOUBLE || pColumnType == Types.FLOAT + || pColumnType == Types.NUMERIC || pColumnType == Types.REAL + || pColumnType == Types.SMALLINT); + } + + public static boolean isDriverAvailable(String pDriver) { + //ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + Class.forName(pDriver, false, null); // null means the caller's ClassLoader + return true; + } + catch (ClassNotFoundException ignore) { + // Ignore + } + return false; + } + + public static void registerDriver(String pDriver) { + // Register JDBC driver + try { + Class.forName(pDriver).newInstance(); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Driver class not found: " + e.getMessage(), e); + //System.err.println("Driver class not found: " + e.getMessage()); + //System.exit(5); + } + catch (InstantiationException e) { + throw new RuntimeException("Driver class could not be instantiated: " + e.getMessage(), e); + //System.err.println("Driver class could not be instantiated: " + e.getMessage()); + //System.exit(5); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Driver class could not be instantiated: " + e.getMessage(), e); + //System.err.println("Driver class could not be instantiated: " + e.getMessage()); + //System.exit(5); + } + } +} \ No newline at end of file diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/package.html b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/package.html new file mode 100755 index 00000000..73bd0b1c --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/package.html @@ -0,0 +1,12 @@ + + + +Provides classes for database access through JDBC. +The package contains warious mechanisms to et connections, read (currently) and write (future) objects from a database, etc. + +@see java.sql +@see com.twelvemonkeys.sql.ObjectReader +@see com.twelvemonkeys.sql.DatabaseConnection + + + diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/util/DebugUtil.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/DebugUtil.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/util/DebugUtil.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/DebugUtil.java diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/MappedBeanFactory.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/MappedBeanFactory.java new file mode 100755 index 00000000..49acf390 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/MappedBeanFactory.java @@ -0,0 +1,456 @@ +/* + * 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.util; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * MappedBeanFactory + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/sandbox/MappedBeanFactory.java#1 $ + */ +public final class MappedBeanFactory { + + // TODO: Map getMap(Object pProxy) + + // TODO: Consider a @NotNull annotation that will allow for throwing IllegalArgumentExceptions + // - Or a more general validator approach for custom fields... + + // NOTE: Specifying default values does not make much sense, as it would be possible to just add values to the map + // in the first place + + // TODO: Replace Converter varargs with new class a PropertyConverterConfiguration + // - setPropertyConverter(String propertyName, Converter from, Converter to) + // - setDefaultConverter(Class from, Class to, Converter) + // TODO: Validators? Allows for more than just NotNull checking + // TODO: Mixin support for other methods, and we are on the way to full-blown AOP.. ;-) + // TODO: Delegate for behaviour? + + // TODO: Consider being fail-fast for primitives without default values? + // Or have default values be the same as they would have been if class members (false/0/null) + // NOTE: There's a difference between a map with a null value for a key, and no presence of that key at all + + // TODO: ProperyChange support! + + private MappedBeanFactory() { + } + + static T as(final Class pClass, final Converter... pConverters) { + // TODO: Add neccessary default initializer stuff here. + return as(pClass, new LinkedHashMap(), pConverters); + } + + @SuppressWarnings({"unchecked"}) + static T as(final Class pClass, final Map pMap, final Converter... pConverters) { + return asImpl(pClass, (Map) pMap, pConverters); + } + + static T asImpl(final Class pClass, final Map pMap, final Converter[] pConverters) { + // TODO: Report clashing? Named converters? + final Map converters = new HashMap() { + @Override + public Converter get(Object key) { + Converter converter = super.get(key); + return converter != null ? converter : Converter.NULL; + } + }; + + for (Converter converter : pConverters) { + converters.put(new ConverterKey(converter.getFromType(), converter.getToType()), converter); + } + + return pClass.cast( + Proxy.newProxyInstance( + pClass.getClassLoader(), + new Class[]{pClass, Serializable.class}, // TODO: Maybe Serializable should be specified by pClass? + new MappedBeanInvocationHandler(pClass, pMap, converters) + ) + ); + } + + private static class ConverterKey { + private Class to; + private Class from; + + ConverterKey(Class pFrom, Class pTo) { + to = pTo; + from = pFrom; + } + + @Override + public boolean equals(Object pOther) { + if (this == pOther) { + return true; + } + if (pOther == null || getClass() != pOther.getClass()) { + return false; + } + + ConverterKey that = (ConverterKey) pOther; + + return from == that.from && to == that.to; + } + + @Override + public int hashCode() { + int result = to != null ? to.hashCode() : 0; + result = 31 * result + (from != null ? from.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return String.format("%s->%s", from, to); + } + } + + public static interface Converter { + + Converter NULL = new Converter() { + public Class getFromType() { + return null; + } + + public Class getToType() { + return null; + } + + public Object convert(Object value, Object old) { + if (value == null) { + return value; + } + throw new ClassCastException(value.getClass().getName()); + } + }; + + Class getFromType(); + + Class getToType(); + + T convert(F value, T old); + } + + // Add guards for null values by throwing IllegalArgumentExceptions for parameters + // TODO: Throw IllegalArgumentException at CREATION time, if value in map is null for a method with @NotNull return type + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + static @interface NotNull { + } + + // For setter methods to have automatic property change support + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + // TODO: Consider field as well? + static @interface Observable { + } + + // TODO: Decide on default value annotation + // Alternate default value annotation + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultValue { + boolean booleanValue() default false; + byte byteValue() default 0; + char charValue() default 0; + short shortValue() default 0; + int intValue() default 0; + float floatValue() default 0f; + long longValue() default 0l; + double doubleValue() default 0d; + } + + + // Default values for primitive types + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultBooleanValue { + boolean value() default false; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultByteValue { + byte value() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultCharValue { + char value() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultShortValue { + short value() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultIntValue { + int value() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultFloatValue { + float value() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultLongValue { + long value() default 0; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + static @interface DefaultDouleValue { + double value() default 0; + } + + // TODO: Does it make sense to NOT just put the value in the map? + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + private static @interface DefaultStringValue { + String value(); // TODO: Do we want a default empty string? + } + + private static class MappedBeanInvocationHandler implements InvocationHandler, Serializable { + + private static final Method OBJECT_TO_STRING = getObjectMethod("toString"); + private static final Method OBJECT_HASH_CODE = getObjectMethod("hashCode"); + private static final Method OBJECT_EQUALS = getObjectMethod("equals", Object.class); + private static final Method OBJECT_CLONE = getObjectMethod("clone"); + + private final Class mClass; + private final Map mMap; + private final Map mConverters; + + private transient Map mReadMethods = new HashMap(); + private transient Map mWriteMethods = new HashMap(); + + private static Method getObjectMethod(final String pMethodName, final Class... pParams) { + try { + return Object.class.getDeclaredMethod(pMethodName, pParams); + } + catch (NoSuchMethodException e) { + throw new Error(e.getMessage(), e); + } + } + + private Object readResolve() throws ObjectStreamException { + mReadMethods = new HashMap(); + mWriteMethods = new HashMap(); + + introspectBean(mClass, mReadMethods, mWriteMethods); + + return this; + } + + public MappedBeanInvocationHandler(Class pClass, Map pMap, Map pConverters) { + mClass = pClass; + mMap = pMap; + mConverters = pConverters; + + introspectBean(mClass, mReadMethods, mWriteMethods); + } + + private void introspectBean(Class pClass, Map pReadMethods, Map pWriteMethods) { + try { + BeanInfo info = Introspector.getBeanInfo(pClass); + PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); + for (PropertyDescriptor descriptor : descriptors) { + String name = descriptor.getName(); + + Method read = descriptor.getReadMethod(); + if (read != null) { + pReadMethods.put(read, name); + } + + Method write = descriptor.getWriteMethod(); + if (write != null) { + pWriteMethods.put(write, name); + } + } + } + catch (IntrospectionException e) { + throw new IllegalArgumentException(String.format("Class %s not introspectable: %s", pClass, e.getMessage()) , e); + } + } + + public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArguments) throws Throwable { + String property = mReadMethods.get(pMethod); + if (property != null) { + if (pArguments == null || pArguments.length == 0) { + Object value = mMap.get(property); + Class type = pMethod.getReturnType(); + + if (!isCompatible(value, type)) { + return mConverters.get(new ConverterKey(value != null ? value.getClass() : Void.class, unBoxType(type))).convert(value, null); + } + return value; + } + else { + throw new IllegalArgumentException("Unknown parameters for " + pMethod + ": " + Arrays.toString(pArguments)); + } + } + + property = mWriteMethods.get(pMethod); + if (property != null) { + if (pArguments.length == 1) { + Object value = pArguments[0]; + + // Make sure we don't accidentally overwrite a value that looks like ours... + Object oldValue = mMap.get(property); + Class type = pMethod.getParameterTypes()[0]; + if (oldValue != null && !isCompatible(oldValue, type)) { + value = mConverters.get(new ConverterKey(type, oldValue.getClass())).convert(value, oldValue); + } + return mMap.put(property, value); + } + else { + throw new IllegalArgumentException("Unknown parameters for " + pMethod + ": " + Arrays.toString(pArguments)); + } + } + + if (pMethod.equals(OBJECT_TO_STRING)) { + return proxyToString(); + } + if (pMethod.equals(OBJECT_EQUALS)) { + return proxyEquals(pProxy, pArguments[0]); + } + if (pMethod.equals(OBJECT_HASH_CODE)) { + return proxyHashCode(); + } + if (pMethod.getName().equals(OBJECT_CLONE.getName()) + && Arrays.equals(pMethod.getParameterTypes(), OBJECT_CLONE.getParameterTypes()) + && OBJECT_CLONE.getReturnType().isAssignableFrom(pMethod.getReturnType())) { + return proxyClone(); + } + + // Other methods not handled (for now) + throw new AbstractMethodError(pMethod.getName()); + } + + private boolean isCompatible(final Object pValue, final Class pType) { + return pValue == null && !pType.isPrimitive() || unBoxType(pType).isInstance(pValue); + } + + private Class unBoxType(final Class pType) { + if (pType.isPrimitive()) { + if (pType == boolean.class) { + return Boolean.class; + } + if (pType == byte.class) { + return Byte.class; + } + if (pType == char.class) { + return Character.class; + } + if (pType == short.class) { + return Short.class; + } + if (pType == int.class) { + return Integer.class; + } + if (pType == float.class) { + return Float.class; + } + if (pType == long.class) { + return Long.class; + } + if (pType == double.class) { + return Double.class; + } + + throw new IllegalArgumentException("Unknown type: " + pType); + } + return pType; + } + + private int proxyHashCode() { + // NOTE: Implies mMap instance must follow Map.equals contract + return mMap.hashCode(); + } + + private boolean proxyEquals(final Object pThisProxy, final Object pThat) { + if (pThisProxy == pThat) { + return true; + } + if (pThat == null) { + return false; + } + + // TODO: Document that subclasses are considered equal (if no extra properties) + if (!mClass.isInstance(pThat)) { + return false; + } + if (!Proxy.isProxyClass(pThat.getClass())) { + return false; + } + + // NOTE: This implies that we should put default values in map at creation time + // NOTE: Implies mMap instance must follow Map.equals contract + InvocationHandler handler = Proxy.getInvocationHandler(pThat); + return handler.getClass() == getClass() && mMap.equals(((MappedBeanInvocationHandler) handler).mMap); + + } + + private Object proxyClone() throws CloneNotSupportedException { + return as( + mClass, + new LinkedHashMap(mMap), + mConverters.values().toArray(new Converter[mConverters.values().size()]) + ); + } + + private String proxyToString() { + return String.format("%s$MapProxy@%s: %s", mClass.getName(), System.identityHashCode(this), mMap); + } + } +} diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java new file mode 100755 index 00000000..c35ece68 --- /dev/null +++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/PersistentMap.java @@ -0,0 +1,61 @@ +/* + * 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.util; + +/** + * PersistentMap + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PersistentMap.java,v 1.0 May 13, 2009 2:31:29 PM haraldk Exp$ + */ +public class PersistentMap { + // TODO: Implement Map + // TODO: Delta synchronization (db?) +} + +/* +Persistent format + +Header + File ID 4-8 bytes + Size + + Entry pointer array block + Size + Next entry pointer block address + Entry 1 address + ... + Entry n address + + Entry 1 + ... + Entry n + +*/ \ No newline at end of file diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/util/XMLProperties.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/XMLProperties.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/util/XMLProperties.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/XMLProperties.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java diff --git a/sandbox/common/src/main/java/com/twelvemonkeys/xml/XMLReader.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/xml/XMLReader.java similarity index 100% rename from sandbox/common/src/main/java/com/twelvemonkeys/xml/XMLReader.java rename to sandbox/sandbox-common/src/main/java/com/twelvemonkeys/xml/XMLReader.java diff --git a/sandbox/common/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java similarity index 100% rename from sandbox/common/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java rename to sandbox/sandbox-common/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java diff --git a/sandbox/common/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java similarity index 100% rename from sandbox/common/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java rename to sandbox/sandbox-common/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java diff --git a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/lang/DuckTypeTestCase.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/lang/DuckTypeTestCase.java new file mode 100755 index 00000000..303db743 --- /dev/null +++ b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/lang/DuckTypeTestCase.java @@ -0,0 +1,246 @@ +package com.twelvemonkeys.lang; + +import junit.framework.TestCase; +import junit.framework.AssertionFailedError; + +/** + * “If it walks like a duck, looks like a duck, quacks like a duck, it must be…” + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/lang/DuckTypeTestCase.java#1 $ + */ +public class DuckTypeTestCase extends TestCase { + + static public interface Eatable { + } + + static public interface Vegetable extends Eatable { + } + + static public interface Meat extends Eatable { + } + + static public interface Animal { + void walk(); + boolean canEat(Eatable pFood); + void eat(Eatable pFood); + } + + static public interface Bird extends Animal { + void fly(); + } + + static public interface Duck extends Bird { + void quack(); + } + + static public class DuckLookALike { + private boolean mWalking; + private boolean mFlying; + private boolean mQuacking; + + public void walk() { + mWalking = true; + } + + public void fly() { + mFlying = true; + } + + public void quack() { + mQuacking = true; + } + + void reset() { + mWalking = mFlying = mQuacking = false; + } + + boolean verify() { + return mWalking && mFlying && mQuacking; + } + } + + static public class Swan extends DuckLookALike { + } + + static public class VeggieEater { + private boolean mHappy; + + public boolean canEat(Eatable pFood) { + return pFood instanceof Vegetable; + } + + public void eat(Eatable pFood) { + if (pFood == this) { + throw new IllegalArgumentException("CantEatMyselfException: duh"); + } + if (!canEat(pFood)) { + throw new NotVegetableException("yuck"); + } + + mHappy = true; + } + + void reset() { + mHappy = false; + } + + boolean verify() { + return mHappy; + } + } + + static class NotVegetableException extends RuntimeException { + public NotVegetableException(String message) { + super(message); + } + } + + public static void testTooManyThingsAtOnce() { + DuckLookALike lookALike = new DuckLookALike(); + VeggieEater veggieEater = new VeggieEater(); + + Object obj = DuckType.implement(new Class[]{Duck.class, Meat.class}, + new Object[]{lookALike, veggieEater}); + assertTrue(obj instanceof Duck); + assertTrue(obj instanceof Meat); + Duck duck = (Duck) obj; + + Bird another = (Bird) DuckType.implement(new Class[]{Duck.class, Meat.class}, + new Object[]{lookALike, veggieEater}); + + Duck uglyDuckling = (Duck) DuckType.implement(new Class[] {Duck.class, Meat.class}, + new Object[] {new Swan(), new VeggieEater()}); + + assertNotNull(duck.toString()); + + assertTrue("Duck is supposed to equal itself (identity crisis)", duck.equals(duck)); + + assertEquals("Duck is supposed to equal other duck with same stuffing", duck, another); + + assertFalse("Some ducks are more equal than others", duck.equals(uglyDuckling)); + + duck.walk(); + duck.quack(); + duck.quack(); + duck.fly(); + + assertTrue("Duck is supposed to quack", lookALike.verify()); + + Vegetable cabbage = new Vegetable() {}; + assertTrue("Duck is supposed to like cabbage", duck.canEat(cabbage)); + duck.eat(cabbage); + assertTrue("Duck is supposed to eat vegetables", veggieEater.verify()); + + veggieEater.reset(); + + Throwable exception = null; + try { + duck.eat((Meat) uglyDuckling); + fail("Duck ate distant cousin"); + } + catch (AssertionFailedError e) { + throw e; + } + catch (Throwable t) { + exception = t; + } + assertTrue("Incorrect quack: " + exception, exception instanceof NotVegetableException); + + + // TODO: There's a flaw in the design here.. + // The "this" keyword don't work well with proxies.. + + // Something that could possibly work, is: + // All proxy-aware delegates need a method getThis() / getSelf()... + // (using a field won't work for delegates that are part of multiple + // proxies). + // The default implementation should only return "this".. + // TBD: How do we know which proxy the current delegate is part of? + + exception = null; + try { + duck.eat((Meat) duck); + fail("Duck ate itself"); + } + catch (AssertionFailedError e) { + throw e; + } + catch (Throwable t) { + exception = t; + } + assertTrue("Duck tried to eat itself: " + exception, exception instanceof IllegalArgumentException); + } + + public void testExpandedArgs() { + Object walker = new Object() { + public void walk() { + } + }; + Object eater = new Object() { + // Assignable, but not direct match + public boolean canEat(Object pFood) { + return true; + } + + // Assignable, but not direct match + public void eat(Object pFood) { + } + }; + + Animal rat = (Animal) DuckType.implement(new Class[]{Animal.class, Meat.class}, + new Object[]{walker, eater}); + + assertNotNull(rat); + assertTrue(rat instanceof Meat); + + // Rats eat everything + Eatable smellyFood = new Eatable() {boolean tastesVeryBad = true;}; + assertTrue("Rat did not eat smelly food", rat.canEat(smellyFood)); + } + + public void testExpandedArgsFail() { + try { + Object walker = new Object() { + public void walk() { + } + }; + Object eater = new Object() { + // Not assignable return type + public int canEat(Eatable pFood) { + return 1; + } + + // Assignable, but not direct match + public void eat(Object pFood) { + } + }; + DuckType.implement(new Class[]{Animal.class}, + new Object[]{walker, eater}); + + fail("This kind of animal won't live long"); + } + catch (DuckType.NoMatchingMethodException e) { + } + } + + public void testStubAbstract() { + Object obj = DuckType.implement(new Class[]{Animal.class}, + new Object[]{new Object()}, true); + assertTrue(obj instanceof Animal); + Animal unicorn = (Animal) obj; + assertNotNull(unicorn); + + // Should create a meaningful string representation + assertNotNull(unicorn.toString()); + + // Unicorns don't fly, as they are only an abstract idea.. + try { + unicorn.walk(); + fail("Unicorns should not fly, as they are only an abstract idea"); + } + catch (AbstractMethodError e) { + } + } +} diff --git a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/MappedBeanFactoryTestCase.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/MappedBeanFactoryTestCase.java new file mode 100755 index 00000000..bf313abd --- /dev/null +++ b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/util/MappedBeanFactoryTestCase.java @@ -0,0 +1,578 @@ +/* + * 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.util; + +import com.twelvemonkeys.util.MappedBeanFactory; +import junit.framework.AssertionFailedError; +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +import java.awt.*; +import java.awt.event.ActionListener; +import java.beans.IntrospectionException; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.io.*; + +/** + * MappedBeanFactoryTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/sandbox/MappedBeanFactoryTestCase.java#1 $ + */ +public class MappedBeanFactoryTestCase { + + public static interface Foo { + boolean isFoo(); + + int getBar(); + void setBar(int bar); + + Rectangle getBounds(); + void setBounds(Rectangle bounds); + } + + public static interface DefaultFoo extends Foo { +// @MappedBeanFactory.DefaultBooleanValue + @MappedBeanFactory.DefaultValue(booleanValue = false) + boolean isFoo(); + + @MappedBeanFactory.DefaultIntValue + int getBar(); + void setBar(int bar); + + Rectangle getBounds(); + void setBounds(Rectangle bounds); + + @SuppressWarnings({"CloneDoesntDeclareCloneNotSupportedException"}) + DefaultFoo clone(); + } + + static interface ObservableFoo extends DefaultFoo { + @MappedBeanFactory.DefaultBooleanValue(true) + boolean isFoo(); + + @MappedBeanFactory.DefaultIntValue(1) + int getBar(); + + @MappedBeanFactory.NotNull + Rectangle getBounds(); + + @MappedBeanFactory.Observable + void setBounds(@MappedBeanFactory.NotNull Rectangle bounds); + + // TODO: This method should be implicitly supported, and throw IllegalArgument, if NoSuchProperty + // TODO: An observable interface to extend? + void addPropertyChangeListener(String property, PropertyChangeListener listener); + } + + @Test + public void testToString() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + assertNotNull(foo); + assertNotNull(foo.toString()); + assertTrue(foo.toString().contains(DefaultFoo.class.getName())); + + // TODO: Consider this: +// assertTrue(foo.toString().contains("foo=false")); +// assertTrue(foo.toString().contains("bar=0")); +// assertTrue(foo.toString().contains("bounds=null")); + } + + @Test + public void testClone() { + DefaultFoo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("foo", true)); + DefaultFoo clone = foo.clone(); + assertNotSame(foo, clone); + assertEquals(foo, clone); + assertEquals(foo.hashCode(), clone.hashCode()); + assertEquals(foo.isFoo(), clone.isFoo()); + } + + @Test + public void testSerializable() throws IOException, ClassNotFoundException { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("foo", true)); + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream outputStream = new ObjectOutputStream(bytes); + outputStream.writeObject(foo); + + ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())); + Foo bar = (Foo) inputStream.readObject(); + + assertNotSame(foo, bar); + assertEquals(foo, bar); + assertEquals(foo.hashCode(), bar.hashCode()); + assertEquals(foo.isFoo(), bar.isFoo()); + } + + @Test + public void testNotEqualsNull() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + + @SuppressWarnings({"ObjectEqualsNull"}) + boolean equalsNull = foo.equals(null); + + assertFalse(equalsNull); + } + + @Test + public void testEqualsSelf() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap() { + @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) + @Override + public boolean equals(Object o) { + throw new AssertionFailedError("Don't need to test map for equals if same object"); + } + }); + + assertTrue(foo.equals(foo)); + } + + @Test + public void testEqualsOther() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + Foo other = MappedBeanFactory.as(DefaultFoo.class); + + assertEquals(foo, other); + } + + @Test + public void testEqualsOtherModifiedSameValue() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap()); + Foo other = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bar", 0))); + + assertEquals(foo, other); + + // No real change + other.setBar(foo.getBar()); + assertTrue(foo.equals(other)); + } + + @Test + public void testNotEqualsOtherModified() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + Foo other = MappedBeanFactory.as(DefaultFoo.class); + + assertEquals(foo, other); + + // Real change + other.setBar(42); + assertFalse(foo.equals(other)); + } + + @Test + public void testEqualsSubclass() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + + Foo sub = MappedBeanFactory.as(ObservableFoo.class); + assertEquals(foo, sub); + } + + @Test + public void testNotEqualsDifferentValues() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + Foo bar = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("bar", true)); + assertFalse(foo.equals(bar)); + } + + @Test + public void testNotEqualsDifferentClass() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class); + ActionListener actionListener = MappedBeanFactory.as(ActionListener.class); + assertFalse(foo.equals(actionListener)); + } + + @Test + public void testBooleanReadOnly() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("foo", true)); + + assertNotNull(foo); + assertEquals(true, foo.isFoo()); + } + + @Test + public void testBooleanEmpty() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap()); + + assertNotNull(foo); + + try { + foo.isFoo(); + fail("Expected NullPointerException"); + } + catch (NullPointerException expected) { + } + } + + @Test + public void testBooleanEmptyWithConverter() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(), new NullBooleanConverter(true)); + + assertNotNull(foo); + + assertEquals(true, foo.isFoo()); + } + + @Test + public void testIntReadOnly() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, Collections.singletonMap("bar", 1)); + + assertNotNull(foo); + assertEquals(1, foo.getBar()); + + try { + foo.setBar(42); + fail("Expected UnsupportedOperationException"); + } + catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testIntReadWrite() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bar", 1))); + + assertNotNull(foo); + assertEquals(1, foo.getBar()); + + foo.setBar(42); + assertEquals(42, foo.getBar()); + } + + @Test + public void testIntNull() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bar", null))); + + assertNotNull(foo); + + // TODO: Handle null-values smarter, maybe throw a better exception? + // TODO: Consider allowing custom initializers? + try { + foo.getBar(); + fail("Expected NullPointerException"); + } + catch (NullPointerException expected) { + } + + foo.setBar(42); + assertEquals(42, foo.getBar()); + } + + @Test + public void testIntNullWithConverter() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bar", null)), new NullIntConverter(1)); + + assertNotNull(foo); + + assertEquals(1, foo.getBar()); + + foo.setBar(42); + assertEquals(42, foo.getBar()); + } + + @Test + public void testIntWrongType() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bar", "1"))); + + assertNotNull(foo); + + // TODO: Handle conversion smarter, maybe throw a better exception? + try { + foo.getBar(); + fail("Expected ClassCastException"); + } + catch (ClassCastException expected) { + } + + // TODO: Should we allow changing type? + try { + foo.setBar(42); + fail("Expected ClassCastException"); + } + catch (ClassCastException expected) { + } + } + + @Test + public void testBounds() { + Rectangle rectangle = new Rectangle(2, 2, 4, 4); + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bounds", rectangle))); + + assertNotNull(foo); + assertEquals(rectangle, foo.getBounds()); + + foo.setBounds(new Rectangle()); + assertEquals(new Rectangle(), foo.getBounds()); + } + + @Test + public void testBoundsNull() { + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("bounds", null))); + + assertNotNull(foo); + assertNull(foo.getBounds()); + + Rectangle rectangle = new Rectangle(2, 2, 4, 4); + foo.setBounds(rectangle); + assertEquals(rectangle, foo.getBounds()); + + foo.setBounds(null); + assertEquals(null, foo.getBounds()); + } + + @Test + public void testBoundsNullWithConverter() { + // TODO: Allow @NotNull annotations, to say that null is not a valid return value/paramter? + Foo foo = MappedBeanFactory.as(ObservableFoo.class, new HashMap(Collections.singletonMap("bounds", null)), new MappedBeanFactory.Converter() { + public Class getFromType() { + return Void.class; + } + + public Class getToType() { + return Rectangle.class; + } + + public Rectangle convert(Void value, Rectangle old) { + return new Rectangle(10, 10, 10, 10); + } + }); + + assertNotNull(foo); + // TODO: The current problem is that null is okay as return value, even if not specified for interface... + assertEquals(new Rectangle(10, 10, 10, 10), foo.getBounds()); + + Rectangle rectangle = new Rectangle(2, 2, 4, 4); + foo.setBounds(rectangle); + assertEquals(rectangle, foo.getBounds()); + } + + @Test + public void testBoundsAsMapWithConverter() throws IntrospectionException { + Rectangle rectangle = new Rectangle(2, 2, 4, 4); + Map recAsMap = new HashMap(); + recAsMap.put("x", 2); + recAsMap.put("y", 2); + recAsMap.put("width", 4); + recAsMap.put("height", 4); + + HashMap map = new HashMap(Collections.singletonMap("bounds", recAsMap)); + + // TODO: Allow for registering superclasses/interfaces like Map... + Foo foo = MappedBeanFactory.as(DefaultFoo.class, map, new MapRectangleConverter(), new RectangleMapConverter()); + + assertNotNull(foo); + + assertEquals(rectangle, foo.getBounds()); + + foo.setBounds(new Rectangle()); + assertEquals(new Rectangle(), foo.getBounds()); + assertEquals(recAsMap, map.get("bounds")); + assertSame(recAsMap, map.get("bounds")); + + // TODO: The converter should maybe not have to handle this + foo.setBounds(null); + assertNull(foo.getBounds()); + assertEquals(recAsMap, map.get("bounds")); + assertSame(recAsMap, map.get("bounds")); + + Rectangle bounds = new Rectangle(1, 1, 1, 1); + foo.setBounds(bounds); + assertEquals(bounds, foo.getBounds()); + assertEquals(1, foo.getBounds().x); + assertEquals(1, foo.getBounds().y); + assertEquals(1, foo.getBounds().width); + assertEquals(1, foo.getBounds().height); + assertEquals(recAsMap, map.get("bounds")); + assertSame(recAsMap, map.get("bounds")); + } + + @Test + public void testSpeed() { + // How many times faster may the direct access be, before we declare failure? + final int threshold = 50; + + Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap(Collections.singletonMap("foo", false))); + + Foo bar = new Foo() { + public boolean isFoo() { + return false; + } + + public int getBar() { + throw new UnsupportedOperationException("Method getBar not implemented"); + } + + public void setBar(int bar) { + throw new UnsupportedOperationException("Method setBar not implemented"); + } + + public Rectangle getBounds() { + throw new UnsupportedOperationException("Method getBounds not implemented"); // TODO: Implement + } + + public void setBounds(Rectangle bounds) { + throw new UnsupportedOperationException("Method setBounds not implemented"); // TODO: Implement + } + }; + + final int warmup = 50005; + final int iter = 2000000; + for (int i = 0; i < warmup; i++) { + if (foo.isFoo()) { + fail(); + } + if (bar.isFoo()) { + fail(); + } + } + + long startProxy = System.nanoTime(); + for (int i = 0; i < iter; i++) { + if (foo.isFoo()) { + fail(); + } + } + long proxyTime = System.nanoTime() - startProxy; + + long startJava = System.nanoTime(); + for (int i = 0; i < iter; i++) { + if (bar.isFoo()) { + fail(); + } + } + long javaTime = System.nanoTime() - startJava; + + assertTrue( + String.format( + "Proxy time (%1$,d ms) greater than %3$d times direct invocation (%2$,d ms)", + proxyTime / 1000, javaTime / 1000, threshold + ), + proxyTime < threshold * javaTime); + } + + private static class MapRectangleConverter implements MappedBeanFactory.Converter { + public Class getFromType() { + return HashMap.class; + } + + public Class getToType() { + return Rectangle.class; + } + + public Rectangle convert(final HashMap pMap, Rectangle pOldValue) { + if (pMap == null || pMap.isEmpty()) { + return null; + } + + Rectangle rectangle = pOldValue != null ? pOldValue : new Rectangle(); + + rectangle.x = (Integer) pMap.get("x"); + rectangle.y = (Integer) pMap.get("y"); + rectangle.width = (Integer) pMap.get("width"); + rectangle.height = (Integer) pMap.get("height"); + + return rectangle; + } + } + + private static class RectangleMapConverter implements MappedBeanFactory.Converter { + public Class getToType() { + return HashMap.class; + } + + public Class getFromType() { + return Rectangle.class; + } + + public HashMap convert(final Rectangle pRectangle, HashMap pOldValue) { + @SuppressWarnings("unchecked") + HashMap map = pOldValue != null ? pOldValue : new HashMap(); + + if (pRectangle != null) { + map.put("x", pRectangle.x); + map.put("y", pRectangle.y); + map.put("width", pRectangle.width); + map.put("height", pRectangle.height); + } + else { + map.remove("x"); + map.remove("y"); + map.remove("width"); + map.remove("height"); + } + + return map; + } + } + + private static class NullIntConverter implements MappedBeanFactory.Converter { + private Integer mInitialValue; + + public NullIntConverter(int pValue) { + mInitialValue = pValue; + } + + public Class getFromType() { + return Void.class; + } + + public Class getToType() { + return Integer.class; + } + + public Integer convert(Void value, Integer old) { + return mInitialValue; + } + } + + private static class NullBooleanConverter implements MappedBeanFactory.Converter { + private Boolean mInitialValue; + + public NullBooleanConverter(boolean pValue) { + mInitialValue = pValue; + } + + public Class getFromType() { + return Void.class; + } + + public Class getToType() { + return Boolean.class; + } + + public Boolean convert(Void value, Boolean old) { + return mInitialValue; + } + } +}