/*
* Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.lang.reflect.*;
import java.lang.ref.WeakReference;
import java.util.*;
import com.sun.java.swing.SwingUtilities3;
import sun.awt.SubRegionShowable;
import sun.java2d.SunGraphics2D;
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
import sun.awt.SunToolkit;
import sun.util.logging.PlatformLogger;
/**
* A PaintManager implementation that uses a BufferStrategy for
* rendering.
*
* @author Scott Violet
*/
class BufferStrategyPaintManager extends RepaintManager.PaintManager {
//
// All drawing is done to a BufferStrategy. At the end of painting
// (endPaint) the region that was painted is flushed to the screen
// (using BufferStrategy.show).
//
// PaintManager.show is overriden to show directly from the
// BufferStrategy (when using blit), if successful true is
// returned and a paint event will not be generated. To avoid
// showing from the buffer while painting a locking scheme is
// implemented. When beginPaint is invoked the field painting is
// set to true. If painting is true and show is invoked we
// immediately return false. This is done to avoid blocking the
// toolkit thread while painting happens. In a similar way when
// show is invoked the field showing is set to true, beginPaint
// will then block until showing is true. This scheme ensures we
// only ever have one thread using the BufferStrategy and it also
// ensures the toolkit thread remains as responsive as possible.
//
// If we're using a flip strategy the contents of the backbuffer may
// have changed and so show only attempts to show from the backbuffer
// if we get a blit strategy.
//
//
// Methods used to create BufferStrategy for Applets.
//
private static Method COMPONENT_CREATE_BUFFER_STRATEGY_METHOD;
private static Method COMPONENT_GET_BUFFER_STRATEGY_METHOD;
private static final PlatformLogger LOGGER = PlatformLogger.getLogger(
"javax.swing.BufferStrategyPaintManager");
/**
* List of BufferInfos. We don't use a Map primarily because
* there are typically only a handful of top level components making
* a Map overkill.
*/
private ArrayList<BufferInfo> bufferInfos;
/**
* Indicates <code>beginPaint</code> has been invoked. This is
* set to true for the life of beginPaint/endPaint pair.
*/
private boolean painting;
/**
* Indicates we're in the process of showing. All painting, on the EDT,
* is blocked while this is true.
*/
private boolean showing;
//
// Region that we need to flush. When beginPaint is called these are
// reset and any subsequent calls to paint/copyArea then update these
// fields accordingly. When endPaint is called we then try and show
// the accumulated region.
// These fields are in the coordinate system of the root.
//
private int accumulatedX;
private int accumulatedY;
private int accumulatedMaxX;
private int accumulatedMaxY;
//
// The following fields are set by prepare
//
/**
* Farthest JComponent ancestor for the current paint/copyArea.
*/
private JComponent rootJ;
/**
* Location of component being painted relative to root.
*/
private int xOffset;
/**
* Location of component being painted relative to root.
*/
private int yOffset;
/**
* Graphics from the BufferStrategy.
*/
private Graphics bsg;
/**
* BufferStrategy currently being used.
*/
private BufferStrategy bufferStrategy;
/**
* BufferInfo corresponding to root.
*/
private BufferInfo bufferInfo;
/**
* Set to true if the bufferInfo needs to be disposed when current
* paint loop is done.
*/
private boolean disposeBufferOnEnd;
private static Method getGetBufferStrategyMethod() {
if (COMPONENT_GET_BUFFER_STRATEGY_METHOD == null) {
getMethods();
}
return COMPONENT_GET_BUFFER_STRATEGY_METHOD;
}
private static Method getCreateBufferStrategyMethod() {
if (COMPONENT_CREATE_BUFFER_STRATEGY_METHOD == null) {
getMethods();
}
return COMPONENT_CREATE_BUFFER_STRATEGY_METHOD;
}
private static void getMethods() {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Object>() {
public Object run() {
try {
COMPONENT_CREATE_BUFFER_STRATEGY_METHOD = Component.class.
getDeclaredMethod("createBufferStrategy",
new Class[] { int.class,
BufferCapabilities.class });
COMPONENT_CREATE_BUFFER_STRATEGY_METHOD.
setAccessible(true);
COMPONENT_GET_BUFFER_STRATEGY_METHOD = Component.class.
getDeclaredMethod("getBufferStrategy");
COMPONENT_GET_BUFFER_STRATEGY_METHOD.setAccessible(true);
} catch (SecurityException e) {
assert false;
} catch (NoSuchMethodException nsme) {
assert false;
}
return null;
}
});
}
BufferStrategyPaintManager() {
bufferInfos = new ArrayList<BufferInfo>(1);
}
//
// PaintManager methods
//
/**
* Cleans up any created BufferStrategies.
*/
protected void dispose() {
// dipose can be invoked at any random time. To avoid
// threading dependancies we do the actual diposing via an
// invokeLater.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
java.util.List<BufferInfo> bufferInfos;
synchronized(BufferStrategyPaintManager.this) {
while (showing) {
try {
BufferStrategyPaintManager.this.wait();
} catch (InterruptedException ie) {
}
}
bufferInfos = BufferStrategyPaintManager.this.bufferInfos;
BufferStrategyPaintManager.this.bufferInfos = null;
}
dispose(bufferInfos);
}
});
}
private void dispose(java.util.List<BufferInfo> bufferInfos) {
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("BufferStrategyPaintManager disposed",
new RuntimeException());
}
if (bufferInfos != null) {
for (BufferInfo bufferInfo : bufferInfos) {
bufferInfo.dispose();
}
}
}
/**
* Shows the specified region of the back buffer. This will return
* true if successful, false otherwise. This is invoked on the
* toolkit thread in response to an expose event.
*/
public boolean show(Container c, int x, int y, int w, int h) {
synchronized(this) {
if (painting) {
// Don't show from backbuffer while in the process of
// painting.
return false;
}
showing = true;
}
try {
BufferInfo info = getBufferInfo(c);
BufferStrategy bufferStrategy;
if (info != null && info.isInSync() &&
(bufferStrategy = info.getBufferStrategy(false)) != null) {
SubRegionShowable bsSubRegion =
(SubRegionShowable)bufferStrategy;
boolean paintAllOnExpose = info.getPaintAllOnExpose();
info.setPaintAllOnExpose(false);
if (bsSubRegion.showIfNotLost(x, y, (x + w), (y + h))) {
return !paintAllOnExpose;
}
// Mark the buffer as needing to be repainted. We don't
// immediately do a repaint as this method will return false
// indicating a PaintEvent should be generated which will
// trigger a complete repaint.
bufferInfo.setContentsLostDuringExpose(true);
}
}
finally {
synchronized(this) {
showing = false;
notifyAll();
}
}
return false;
}
public boolean paint(JComponent paintingComponent,
JComponent bufferComponent, Graphics g,
int x, int y, int w, int h) {
Container root = fetchRoot(paintingComponent);
if (prepare(paintingComponent, root, true, x, y, w, h)) {
if ((g instanceof SunGraphics2D) &&
((SunGraphics2D)g).getDestination() == root) {
// BufferStrategy may have already constrained the Graphics. To
// account for that we revert the constrain, then apply a
// constrain for Swing on top of that.
int cx = ((SunGraphics2D)bsg).constrainX;
int cy = ((SunGraphics2D)bsg).constrainY;
if (cx != 0 || cy != 0) {
bsg.translate(-cx, -cy);
}
((SunGraphics2D)bsg).constrain(xOffset + cx, yOffset + cy,
x + w, y + h);
bsg.setClip(x, y, w, h);
paintingComponent.paintToOffscreen(bsg, x, y, w, h,
x + w, y + h);
accumulate(xOffset + x, yOffset + y, w, h);
return true;
} else {
// Assume they are going to eventually render to the screen.
// This disables showing from backbuffer until a complete
// repaint occurs.
bufferInfo.setInSync(false);
// Fall through to old rendering.
}
}
// Invalid root, do what Swing has always done.
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("prepare failed");
}
return super.paint(paintingComponent, bufferComponent, g, x, y, w, h);
}
public void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
int deltaX, int deltaY, boolean clip) {
// Note: this method is only called internally and we know that
// g is from a heavyweight Component, so no check is necessary as
// it is in paint() above.
//
// If the buffer isn't in sync there is no point in doing a copyArea,
// it has garbage.
Container root = fetchRoot(c);
if (prepare(c, root, false, 0, 0, 0, 0) && bufferInfo.isInSync()) {
if (clip) {
Rectangle cBounds = c.getVisibleRect();
int relX = xOffset + x;
int relY = yOffset + y;
bsg.clipRect(xOffset + cBounds.x,
yOffset + cBounds.y,
cBounds.width, cBounds.height);
bsg.copyArea(relX, relY, w, h, deltaX, deltaY);
}
else {
bsg.copyArea(xOffset + x, yOffset + y, w, h, deltaX,
deltaY);
}
accumulate(x + xOffset + deltaX, y + yOffset + deltaY, w, h);
} else {
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("copyArea: prepare failed or not in sync");
}
// Prepare failed, or not in sync. By calling super.copyArea
// we'll copy on screen. We need to flush any pending paint to
// the screen otherwise we'll do a copyArea on the wrong thing.
if (!flushAccumulatedRegion()) {
// Flush failed, copyArea will be copying garbage,
// force repaint of all.
rootJ.repaint();
} else {
super.copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
}
}
}
public void beginPaint() {
synchronized(this) {
painting = true;
// Make sure another thread isn't attempting to show from
// the back buffer.
while(showing) {
try {
wait();
} catch (InterruptedException ie) {
}
}
}
if (LOGGER.isLoggable(PlatformLogger.Level.FINEST)) {
LOGGER.finest("beginPaint");
}
// Reset the area that needs to be painted.
resetAccumulated();
}
public void endPaint() {
if (LOGGER.isLoggable(PlatformLogger.Level.FINEST)) {
LOGGER.finest("endPaint: region " + accumulatedX + " " +
accumulatedY + " " + accumulatedMaxX + " " +
accumulatedMaxY);
}
if (painting) {
if (!flushAccumulatedRegion()) {
if (!isRepaintingRoot()) {
repaintRoot(rootJ);
}
else {
// Contents lost twice in a row, punt.
resetDoubleBufferPerWindow();
// In case we've left junk on the screen, force a repaint.
rootJ.repaint();
}
}
}
BufferInfo toDispose = null;
synchronized(this) {
painting = false;
if (disposeBufferOnEnd) {
disposeBufferOnEnd = false;
toDispose = bufferInfo;
bufferInfos.remove(toDispose);
}
}
if (toDispose != null) {
toDispose.dispose();
}
}
/**
* Renders the BufferStrategy to the screen.
*
* @return true if successful, false otherwise.
*/
private boolean flushAccumulatedRegion() {
boolean success = true;
if (accumulatedX != Integer.MAX_VALUE) {
SubRegionShowable bsSubRegion = (SubRegionShowable)bufferStrategy;
boolean contentsLost = bufferStrategy.contentsLost();
if (!contentsLost) {
bsSubRegion.show(accumulatedX, accumulatedY,
accumulatedMaxX, accumulatedMaxY);
contentsLost = bufferStrategy.contentsLost();
}
if (contentsLost) {
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("endPaint: contents lost");
}
// Shown region was bogus, mark buffer as out of sync.
bufferInfo.setInSync(false);
success = false;
}
}
resetAccumulated();
return success;
}
private void resetAccumulated() {
accumulatedX = Integer.MAX_VALUE;
accumulatedY = Integer.MAX_VALUE;
accumulatedMaxX = 0;
accumulatedMaxY = 0;
}
/**
* Invoked when the double buffering or useTrueDoubleBuffering
* changes for a JRootPane. If the rootpane is not double
* buffered, or true double buffering changes we throw out any
* cache we may have.
*/
public void doubleBufferingChanged(final JRootPane rootPane) {
if ((!rootPane.isDoubleBuffered() ||
!rootPane.getUseTrueDoubleBuffering()) &&
rootPane.getParent() != null) {
if (!SwingUtilities.isEventDispatchThread()) {
Runnable updater = new Runnable() {
public void run() {
doubleBufferingChanged0(rootPane);
}
};
SwingUtilities.invokeLater(updater);
}
else {
doubleBufferingChanged0(rootPane);
}
}
}
/**
* Does the work for doubleBufferingChanged.
*/
private void doubleBufferingChanged0(JRootPane rootPane) {
// This will only happen on the EDT.
BufferInfo info;
synchronized(this) {
// Make sure another thread isn't attempting to show from
// the back buffer.
while(showing) {
try {
wait();
} catch (InterruptedException ie) {
}
}
info = getBufferInfo(rootPane.getParent());
if (painting && bufferInfo == info) {
// We're in the process of painting and the user grabbed
// the Graphics. If we dispose now, endPaint will attempt
// to show a bogus BufferStrategy. Set a flag so that
// endPaint knows it needs to dispose this buffer.
disposeBufferOnEnd = true;
info = null;
} else if (info != null) {
bufferInfos.remove(info);
}
}
if (info != null) {
info.dispose();
}
}
/**
* Calculates information common to paint/copyArea.
*
* @return true if should use buffering per window in painting.
*/
private boolean prepare(JComponent c, Container root, boolean isPaint, int x, int y,
int w, int h) {
if (bsg != null) {
bsg.dispose();
bsg = null;
}
bufferStrategy = null;
if (root != null) {
boolean contentsLost = false;
BufferInfo bufferInfo = getBufferInfo(root);
if (bufferInfo == null) {
contentsLost = true;
bufferInfo = new BufferInfo(root);
bufferInfos.add(bufferInfo);
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("prepare: new BufferInfo: " + root);
}
}
this.bufferInfo = bufferInfo;
if (!bufferInfo.hasBufferStrategyChanged()) {
bufferStrategy = bufferInfo.getBufferStrategy(true);
if (bufferStrategy != null) {
bsg = bufferStrategy.getDrawGraphics();
if (bufferStrategy.contentsRestored()) {
contentsLost = true;
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("prepare: contents restored in prepare");
}
}
}
else {
// Couldn't create BufferStrategy, fallback to normal
// painting.
return false;
}
if (bufferInfo.getContentsLostDuringExpose()) {
contentsLost = true;
bufferInfo.setContentsLostDuringExpose(false);
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("prepare: contents lost on expose");
}
}
if (isPaint && c == rootJ && x == 0 && y == 0 &&
c.getWidth() == w && c.getHeight() == h) {
bufferInfo.setInSync(true);
}
else if (contentsLost) {
// We either recreated the BufferStrategy, or the contents
// of the buffer strategy were restored. We need to
// repaint the root pane so that the back buffer is in sync
// again.
bufferInfo.setInSync(false);
if (!isRepaintingRoot()) {
repaintRoot(rootJ);
}
else {
// Contents lost twice in a row, punt
resetDoubleBufferPerWindow();
}
}
return (bufferInfos != null);
}
}
return false;
}
private Container fetchRoot(JComponent c) {
boolean encounteredHW = false;
rootJ = c;
Container root = c;
xOffset = yOffset = 0;
while (root != null &&
(!(root instanceof Window) &&
!SunToolkit.isInstanceOf(root, "java.applet.Applet"))) {
xOffset += root.getX();
yOffset += root.getY();
root = root.getParent();
if (root != null) {
if (root instanceof JComponent) {
rootJ = (JComponent)root;
}
else if (!root.isLightweight()) {
if (!encounteredHW) {
encounteredHW = true;
}
else {
// We've encountered two hws now and may have
// a containment hierarchy with lightweights containing
// heavyweights containing other lightweights.
// Heavyweights poke holes in lightweight
// rendering so that if we call show on the BS
// (which is associated with the Window) you will
// not see the contents over any child
// heavyweights. If we didn't do this when we
// went to show the descendants of the nested hw
// you would see nothing, so, we bail out here.
return null;
}
}
}
}
if ((root instanceof RootPaneContainer) &&
(rootJ instanceof JRootPane)) {
// We're in a Swing heavyeight (JFrame/JWindow...), use double
// buffering if double buffering enabled on the JRootPane and
// the JRootPane wants true double buffering.
if (rootJ.isDoubleBuffered() &&
((JRootPane)rootJ).getUseTrueDoubleBuffering()) {
// Whether or not a component is double buffered is a
// bit tricky with Swing. This gives a good approximation
// of the various ways to turn on double buffering for
// components.
return root;
}
}
// Don't do true double buffering.
return null;
}
/**
* Turns off double buffering per window.
*/
private void resetDoubleBufferPerWindow() {
if (bufferInfos != null) {
dispose(bufferInfos);
bufferInfos = null;
repaintManager.setPaintManager(null);
}
}
/**
* Returns the BufferInfo for the specified root or null if one
* hasn't been created yet.
*/
private BufferInfo getBufferInfo(Container root) {
for (int counter = bufferInfos.size() - 1; counter >= 0; counter--) {
BufferInfo bufferInfo = bufferInfos.get(counter);
Container biRoot = bufferInfo.getRoot();
if (biRoot == null) {
// Window gc'ed
bufferInfos.remove(counter);
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("BufferInfo pruned, root null");
}
}
else if (biRoot == root) {
return bufferInfo;
}
}
return null;
}
private void accumulate(int x, int y, int w, int h) {
accumulatedX = Math.min(x, accumulatedX);
accumulatedY = Math.min(y, accumulatedY);
accumulatedMaxX = Math.max(accumulatedMaxX, x + w);
accumulatedMaxY = Math.max(accumulatedMaxY, y + h);
}
/**
* BufferInfo is used to track the BufferStrategy being used for
* a particular Component. In addition to tracking the BufferStrategy
* it will install a WindowListener and ComponentListener. When the
* component is hidden/iconified the buffer is marked as needing to be
* completely repainted.
*/
private class BufferInfo extends ComponentAdapter implements
WindowListener {
// NOTE: This class does NOT hold a direct reference to the root, if it
// did there would be a cycle between the BufferPerWindowPaintManager
// and the Window so that it could never be GC'ed
//
// Reference to BufferStrategy is referenced via WeakReference for
// same reason.
private WeakReference<BufferStrategy> weakBS;
private WeakReference<Container> root;
// Indicates whether or not the backbuffer and display are in sync.
// This is set to true when a full repaint on the rootpane is done.
private boolean inSync;
// Indicates the contents were lost during and expose event.
private boolean contentsLostDuringExpose;
// Indicates we need to generate a paint event on expose.
private boolean paintAllOnExpose;
public BufferInfo(Container root) {
this.root = new WeakReference<Container>(root);
root.addComponentListener(this);
if (root instanceof Window) {
((Window)root).addWindowListener(this);
}
}
public void setPaintAllOnExpose(boolean paintAllOnExpose) {
this.paintAllOnExpose = paintAllOnExpose;
}
public boolean getPaintAllOnExpose() {
return paintAllOnExpose;
}
public void setContentsLostDuringExpose(boolean value) {
contentsLostDuringExpose = value;
}
public boolean getContentsLostDuringExpose() {
return contentsLostDuringExpose;
}
public void setInSync(boolean inSync) {
this.inSync = inSync;
}
/**
* Whether or not the contents of the buffer strategy
* is in sync with the window. This is set to true when the root
* pane paints all, and false when contents are lost/restored.
*/
public boolean isInSync() {
return inSync;
}
/**
* Returns the Root (Window or Applet) that this BufferInfo references.
*/
public Container getRoot() {
return (root == null) ? null : root.get();
}
/**
* Returns the BufferStartegy. This will return null if
* the BufferStartegy hasn't been created and <code>create</code> is
* false, or if there is a problem in creating the
* <code>BufferStartegy</code>.
*
* @param create If true, and the BufferStartegy is currently null,
* one will be created.
*/
public BufferStrategy getBufferStrategy(boolean create) {
BufferStrategy bs = (weakBS == null) ? null : weakBS.get();
if (bs == null && create) {
bs = createBufferStrategy();
if (bs != null) {
weakBS = new WeakReference<BufferStrategy>(bs);
}
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("getBufferStrategy: created bs: " + bs);
}
}
return bs;
}
/**
* Returns true if the buffer strategy of the component differs
* from current buffer strategy.
*/
public boolean hasBufferStrategyChanged() {
Container root = getRoot();
if (root != null) {
BufferStrategy ourBS = null;
BufferStrategy componentBS = null;
ourBS = getBufferStrategy(false);
if (root instanceof Window) {
componentBS = ((Window)root).getBufferStrategy();
}
else {
try {
componentBS = (BufferStrategy)
getGetBufferStrategyMethod().invoke(root);
} catch (InvocationTargetException ite) {
assert false;
} catch (IllegalArgumentException iae) {
assert false;
} catch (IllegalAccessException iae2) {
assert false;
}
}
if (componentBS != ourBS) {
// Component has a different BS, dispose ours.
if (ourBS != null) {
ourBS.dispose();
}
weakBS = null;
return true;
}
}
return false;
}
/**
* Creates the BufferStrategy. If the appropriate system property
* has been set we'll try for flip first and then we'll try for
* blit.
*/
private BufferStrategy createBufferStrategy() {
Container root = getRoot();
if (root == null) {
return null;
}
BufferStrategy bs = null;
if (SwingUtilities3.isVsyncRequested(root)) {
bs = createBufferStrategy(root, true);
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
LOGGER.finer("createBufferStrategy: using vsynced strategy");
}
}
if (bs == null) {
bs = createBufferStrategy(root, false);
}
if (!(bs instanceof SubRegionShowable)) {
// We do this for two reasons:
// 1. So that we know we can cast to SubRegionShowable and
// invoke show with the minimal region to update
// 2. To avoid the possibility of invoking client code
// on the toolkit thread.
bs = null;
}
return bs;
}
// Creates and returns a buffer strategy. If
// there is a problem creating the buffer strategy this will
// eat the exception and return null.
private BufferStrategy createBufferStrategy(Container root,
boolean isVsynced) {
BufferCapabilities caps;
if (isVsynced) {
caps = new ExtendedBufferCapabilities(
new ImageCapabilities(true), new ImageCapabilities(true),
BufferCapabilities.FlipContents.COPIED,
ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
} else {
caps = new BufferCapabilities(
new ImageCapabilities(true), new ImageCapabilities(true),
null);
}
BufferStrategy bs = null;
if (SunToolkit.isInstanceOf(root, "java.applet.Applet")) {
try {
getCreateBufferStrategyMethod().invoke(root, 2, caps);
bs = (BufferStrategy)getGetBufferStrategyMethod().
invoke(root);
} catch (InvocationTargetException ite) {
// Type is not supported
if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
/**代码未完, 请加载全部代码(NowJava.com).**/