mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
Clean-up of sandbox, rearranging everything.
Added a couple of files that was never commited.
This commit is contained in:
parent
1f60b62626
commit
b6ee5ce450
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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?
|
||||||
|
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileMonitor.java
Executable file
57
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/FileMonitor.java
Executable file
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
468
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/DuckType.java
Executable file
468
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/DuckType.java
Executable file
@ -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…”.
|
||||||
|
* <p/>
|
||||||
|
* Based on an idea found at
|
||||||
|
* <a href="http://www.coconut-palm-software.com/the_visual_editor/?p=25">The Visual Editor</a>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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...
|
||||||
|
* <P/>
|
||||||
|
* Tested with jConnect (Sybase), I-net Sprinta2000 (MS SQL) and Oracle.
|
||||||
|
* <P/>
|
||||||
|
* @todo be able to register more drivers, trough properties and runtime
|
||||||
|
* @todo be able to register more connections, trough properties and runtime
|
||||||
|
* <P/>
|
||||||
|
* <STRONG>Example properties file</STRONG></BR>
|
||||||
|
* # 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
126
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java
Executable file
126
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/DatabaseProduct.java
Executable file
@ -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
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
173
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java
Executable file
173
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/JDBCHelper.java
Executable file
@ -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
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
673
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/Log.java
Executable file
673
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/Log.java
Executable file
@ -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).
|
||||||
|
* <P>
|
||||||
|
* 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.
|
||||||
|
* <P>
|
||||||
|
* <STRONG>
|
||||||
|
* 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.
|
||||||
|
* </STRONG>
|
||||||
|
* <P>
|
||||||
|
* <STRONG>
|
||||||
|
* WARNING: The cached OutputStreams can possibly be in error state or be
|
||||||
|
* closed without warning. Should be fixed in later versions!
|
||||||
|
* </STRONG>
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* <P>
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
276
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectManager.java
Executable file
276
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectManager.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
663
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java
Executable file
663
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectMapper.java
Executable file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
879
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectReader.java
Executable file
879
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/ObjectReader.java
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
396
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/SQLUtil.java
Executable file
396
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/SQLUtil.java
Executable file
@ -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. <EM>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.</EM>
|
||||||
|
* <P/>
|
||||||
|
* <STRONG>Exmaple use</STRONG>
|
||||||
|
* <BR/>
|
||||||
|
* <PRE>
|
||||||
|
* $ 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"</PRE>
|
||||||
|
* <EM>Make sure sure to include the path to your JDBC driver in the java class
|
||||||
|
* path!</EM>
|
||||||
|
*
|
||||||
|
* @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 <login-name>] [--password|-p <password>] [--driver|-d <jdbc-driver-class>] [--url|-u <connect url>] [--config|-c <config-file>] [--script|-s <script-file>] <sql statement> ");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/package.html
Executable file
12
sandbox/sandbox-common/src/main/java/com/twelvemonkeys/sql/package.html
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
<HTML>
|
||||||
|
|
||||||
|
<BODY>
|
||||||
|
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
|
||||||
|
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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<String, ?> 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> T as(final Class<T> pClass, final Converter... pConverters) {
|
||||||
|
// TODO: Add neccessary default initializer stuff here.
|
||||||
|
return as(pClass, new LinkedHashMap<String, Object>(), pConverters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
static <T> T as(final Class<T> pClass, final Map<String, ?> pMap, final Converter... pConverters) {
|
||||||
|
return asImpl(pClass, (Map<String, Object>) pMap, pConverters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> T asImpl(final Class<T> pClass, final Map<String, Object> pMap, final Converter[] pConverters) {
|
||||||
|
// TODO: Report clashing? Named converters?
|
||||||
|
final Map<ConverterKey, Converter> converters = new HashMap<ConverterKey, Converter>() {
|
||||||
|
@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<F, T> {
|
||||||
|
|
||||||
|
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<F> getFromType();
|
||||||
|
|
||||||
|
Class<T> 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<String, Object> mMap;
|
||||||
|
private final Map<ConverterKey, Converter> mConverters;
|
||||||
|
|
||||||
|
private transient Map<Method, String> mReadMethods = new HashMap<Method, String>();
|
||||||
|
private transient Map<Method, String> mWriteMethods = new HashMap<Method, String>();
|
||||||
|
|
||||||
|
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<Method, String>();
|
||||||
|
mWriteMethods = new HashMap<Method, String>();
|
||||||
|
|
||||||
|
introspectBean(mClass, mReadMethods, mWriteMethods);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappedBeanInvocationHandler(Class<?> pClass, Map<String, Object> pMap, Map<ConverterKey, Converter> pConverters) {
|
||||||
|
mClass = pClass;
|
||||||
|
mMap = pMap;
|
||||||
|
mConverters = pConverters;
|
||||||
|
|
||||||
|
introspectBean(mClass, mReadMethods, mWriteMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void introspectBean(Class<?> pClass, Map<Method, String> pReadMethods, Map<Method, String> 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<String, Object>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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
|
||||||
|
|
||||||
|
*/
|
@ -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…”
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @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<String, Object>() {
|
||||||
|
@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<String, Object>());
|
||||||
|
Foo other = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(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<String, Object>());
|
||||||
|
|
||||||
|
assertNotNull(foo);
|
||||||
|
|
||||||
|
try {
|
||||||
|
foo.isFoo();
|
||||||
|
fail("Expected NullPointerException");
|
||||||
|
}
|
||||||
|
catch (NullPointerException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBooleanEmptyWithConverter() {
|
||||||
|
Foo foo = MappedBeanFactory.as(DefaultFoo.class, new HashMap<String, Object>(), 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<String, Object>(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<String, Object>(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<String, Object>(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<String, Object>(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<String, Object>(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<String, Object>(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<String, Object>(Collections.singletonMap("bounds", null)), new MappedBeanFactory.Converter<Void, Rectangle>() {
|
||||||
|
public Class<Void> getFromType() {
|
||||||
|
return Void.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Rectangle> 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<String, Object> recAsMap = new HashMap<String, Object>();
|
||||||
|
recAsMap.put("x", 2);
|
||||||
|
recAsMap.put("y", 2);
|
||||||
|
recAsMap.put("width", 4);
|
||||||
|
recAsMap.put("height", 4);
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>(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<String, Object>(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<HashMap, Rectangle> {
|
||||||
|
public Class<HashMap> getFromType() {
|
||||||
|
return HashMap.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Rectangle> 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<Rectangle, HashMap> {
|
||||||
|
public Class<HashMap> getToType() {
|
||||||
|
return HashMap.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Rectangle> getFromType() {
|
||||||
|
return Rectangle.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap convert(final Rectangle pRectangle, HashMap pOldValue) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
HashMap<String, Integer> map = pOldValue != null ? pOldValue : new HashMap<String, Integer>();
|
||||||
|
|
||||||
|
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<Void, Integer> {
|
||||||
|
private Integer mInitialValue;
|
||||||
|
|
||||||
|
public NullIntConverter(int pValue) {
|
||||||
|
mInitialValue = pValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Void> getFromType() {
|
||||||
|
return Void.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Integer> getToType() {
|
||||||
|
return Integer.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer convert(Void value, Integer old) {
|
||||||
|
return mInitialValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NullBooleanConverter implements MappedBeanFactory.Converter<Void, Boolean> {
|
||||||
|
private Boolean mInitialValue;
|
||||||
|
|
||||||
|
public NullBooleanConverter(boolean pValue) {
|
||||||
|
mInitialValue = pValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Void> getFromType() {
|
||||||
|
return Void.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<Boolean> getToType() {
|
||||||
|
return Boolean.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean convert(Void value, Boolean old) {
|
||||||
|
return mInitialValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user