.
.
java.sun.com developers.sun.com
.
   View this issue as simple text May 18, 2004    

In this Issue

Welcome to the Core Java Technologies Tech Tips for May 18, 2004. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

-Understanding Rendering Hints
-Creating Custom Security Permissions

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.2.

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

For more Java technology content, visit these sites:

java.sun.com - The latest Java platform releases, tutorials, and newsletters.

java.net - A web forum for collaborating and building solutions together.

java.com - The marketplace for Java technology, applications and services.

.
.

UNDERSTANDING RENDERING HINTS

One of the ways you can perform drawing-related tasks with the standard Java libraries is through the Graphics object. By passing a Graphics object into the paint() and paintComponent() method of your component, you can draw with methods such as drawLine, drawRect, and drawOval. These Graphics-based operations are fairly basic -- they typically draw with a single color on a line that is a single pixel wide. Here's an example:

    import java.awt.*;
    import javax.swing.*;

    public class Draw1 extends JFrame {
      public static void main(String args[]) {
        Runnable runner = new Runnable() {
          public void run() {
            JFrame frame = new Draw1();
            frame.getContentPane().add(new JComponent() {
              public void paintComponent(Graphics g) {
                String s = "Hello, World";
                FontMetrics metrics = g.getFontMetrics();
                int width = metrics.stringWidth(s);
                g.drawString(s, 100, 100);
                g.drawLine(100, 100, 100+width, 100);
              }
            });
            frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
            frame.setSize(300, 200);
            frame.setVisible(true);
          }
        };
        EventQueue.invokeLater(runner);
      }
    }

The Graphics object you pass into the paintComponent method is actually an instance of the subclass Graphics2D. By casting this object to a Graphics2D instance, you can use the richer drawing operations supported by the Java 2D API:

    public void paintComponent(Graphics g) {
         Graphics2D g2d = (Graphics2D)g;
         ...
    }

Instead of drawing single pixel lines of a single color, you can draw more complex patterns. See the May 20, 2003 Tech Tip Drawing Dashed Lines with Stroke for information on drawing with other than solid lines.

Not only does the Graphics2D API support richer drawing operations, but it allows you to control some aspects of rendering. You set the controls through hints sent to the Graphics2D object. The RenderingHints class provides all of the possible hint keys and values as class constants. You use the setRenderingHint method of Graphics2D to set the hints.

The setRenderingHint method works with two halves of the RenderingHints class: a key and a value:

   Graphics2D g2d = (Graphics2D)g;
   g2d.setRenderingHint(aKey, aValue);

The key part is of type RenderingHints.KEY. The RenderingHints class offers nine possible keys:

  • KEY_ALPHA_INTERPOLATION
  • KEY_ANTIALIASING
  • KEY_COLOR_RENDERING
  • KEY_DITHERING
  • KEY_FRACTIONALMETRICS
  • KEY_INTERPOLATION
  • KEY_RENDERING
  • KEY_STROKE_CONTROL
  • KEY_TEXT_ANTIALIASING

For each key, the RenderingHints class offers class constants for the possible values. These all begin with VALUE_. The naming conventions are rather obvious, so by looking at their names, you can see which keys and values are connected. For instance, the values for KEY_TEXT_ANTIALIASING are VALUE_TEXT_ANTIALIAS_DEFAULT, VALUE_TEXT_ANTIALIAS_OFF, and VALUE_TEXT_ANTIALIAS_ON. There are really only two settings here: on and off. The default setting allows the platform to decide which of the two is the default mode.

Setting a hint is much like setting a new color or font in a Graphics object. Its value will be preserved until another call is made to set its value again or until the Graphics object is disposed. Each key is tracked separately so that changes to one key do not affect any of the values stored for any of the other keys.

The Graphics2D object includes two methods for setting hints, the setRenderingHint method, for setting a single hint, and a setRenderingHints version for setting multiple hints. The latter version accepts a Map as its argument. The RenderingHints class implements the Map interface, so before calling setRenderingHints, you can build a Map of hints by simply adding each hint separately.

   Graphics2D g2d = (Graphics2D)g;
   RenderingHints hints = 
     new RenderingHints(
       RenderingHints.KEY_TEXT_ANTIALIASING, 
         RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
   hints.put(
     RenderingHints.KEY_DITHERING,
     RenderingHints.VALUE_DITHER_ENABLE);
   g2d.setRenderingHints(hints);

To build a Map of rendering hints, typically you would not call any of the RenderingHints methods.

The different key-value pairs of hints allows your programs to operate slightly differently, potentially with a different performance profile. It is also possible that a platform could ignore the hints. That's because a hint is literally that: a hint, and not a required operation. For instance, the initial release of J2SE 1.2 ignored the alpha interpolation hint.

KEY_ALPHA_INTERPOLATION

The possible values are:

  • VALUE_ALPHA_INTERPOLATION_DEFAULT
  • VALUE_ALPHA_INTERPOLATION_QUALITY
  • VALUE_ALPHA_INTERPOLATION_SPEED

The color at each pixel is made up of four parts: the red, green, and blue parts that control the coloration of a pixel, and the alpha setting that controls its transparency. This hint controls how partially-transparent drawing operations are composited.

KEY_ANTIALIASING

The possible values are:

  • VALUE_ANTIALIAS_DEFAULT
  • VALUE_ANTIALIAS_OFF
  • VALUE_ANTIALIAS_ON

KEY_TEXT_ANTIALIASING

The possible values are:

  • VALUE_TEXT_ANTIALIAS_DEFAULT
  • VALUE_TEXT_ANTIALIAS_OFF
  • VALUE_TEXT_ANTIALIAS_ON

Because device pixels occupy an area on the screen, they can appear either partially or completely inside or outside an outline to be drawn. Typical rendering algorithms classify pixels as either inside or outside, depending on how the outline of the shape crosses or intersects that pixel. The algorithms only paint those pixels that are classified as falling inside. As a result, if you use a single color such as black for the drawing operation, it produces a figure with jagged edges. A better way to draw the shape is to use varying shades of gray to reduce the jagged edges in areas where a pixel doesn't reach the end of the desired shape. This method of rendering is called antialiasing because it eliminates or reduces the jagged edges caused by the aliasing of the regular rendering algorithms.

There are two antialiasing hints available, one for text and one for all drawing operations. When the text hint value is set to VALUE_TEXT_ANTIALIAS_DEFAULT, its default, the value of the main antialiasing hint, KEY_ANTIALIASING, affects text, too. So, turning on the main antialiasing operation (KEY_ANTIALIASING, VALUE_ANTIALIAS_ON), turns on text antialiasing (in addition to other operations such as lines). To turn off text antialiasing (but leave on shape antialiasing), you need to set the text antialias hint value to off (as opposed to leaving it at the default setting).

The following program demonstrates drawing with (1) normal (no antialiasing, or at least the default), (2) text only antialiasing, (3) shape only antialiasing, and (4) all drawing operations antialiased.

    import java.awt.*;
    import java.awt.geom.*;
    import javax.swing.*;

    public class Aliased extends JFrame  {

      final static Stroke stroke = new BasicStroke(2f,
              BasicStroke.CAP_ROUND,
              BasicStroke.JOIN_ROUND, 3f,
              new float[] {10f}, 0f);
      final static Font font = new Font("Serif", Font.ITALIC, 50);
      final static Arc2D arc = new Arc2D.Double(5, 0, 200, 100,
              0, 360, Arc2D.PIE);

      public static void main(String args[]) {
        Runnable runner = new Runnable() {
          public void run() {
            JFrame frame = new Aliased();
            frame.getContentPane().add(new JComponent() {
              public void paintComponent(Graphics g) {
                Graphics2D g2d = (Graphics2D)g;
                g2d.setColor(Color.BLACK);
                for (int i=0; i < 4; i++) {
                  // Set hints for this pass
                  switch (i) {
                    case 0: // Use defaults
                      break;
                    case 1:
                      g2d.setRenderingHint(
                        RenderingHints.KEY_TEXT_ANTIALIASING,
                        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                      break;
                    case 2:
                      RenderingHints hints = new RenderingHints(
                        RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
                      hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
                        RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
                      g2d.setRenderingHints(hints);
                      break;
                    case 3:
                      g2d.setRenderingHint(
                        RenderingHints.KEY_TEXT_ANTIALIASING,
                        RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
                      break;
                  }
                  g2d.setStroke(stroke);
                  g2d.draw(arc);
                  g2d.setFont(font);
                  g2d.drawString("My", 25, 70);
                  g2d.translate(0, 100);
                }
              }
            });
            frame.setDefaultCloseOperation(
              JFrame.EXIT_ON_CLOSE);
            frame.setSize(225, 435);
            frame.show();
          }
        };
        EventQueue.invokeLater(runner);
      }
    }
 

Notice that the drawing in the Aliased program is in a for loop. Because of this, the setRenderingHint method calls build on each other. For the last case, text aliasing is set back to the default setting. This allows the regular antialiasing hint (which was already turned on in a previous loop) to now affect text as well.

Although antialiasing everything makes the output look better, the operation does incur a performance hit (unless there is hardware support). This is due to the calculations necessary to determine the appropriate drawing edge colorations.

KEY_COLOR_RENDERING

The possible values are:

  • VALUE_COLOR_RENDER_DEFAULT
  • VALUE_COLOR_RENDER_QUALITY
  • VALUE_COLOR_RENDER_SPEED

KEY_RENDERING

The possible values are:

  • VALUE_RENDER_DEFAULT
  • VALUE_RENDER_QUALITY
  • VALUE_RENDER_SPEED

The two sets of rendering hints control whether rendering should be done for speed or quality.

KEY_DITHERING

The possible values are:

  • VALUE_DITHER_DEFAULT
  • VALUE_DITHER_DISABLE
  • VALUE_DITHER_ENABLE

Dithering hints control how colors get approximated when the display profile doesn't support the specific color you request. The Java platform only requires 8-bit (256) color support. The Color class allows you to specify 8 bits for each of red, green, and blue, so it is possible to use a Color object that isn't supported on the user's platform.

KEY_FRACTIONALMETRICS

The possible values are:

  • VALUE_FRACTIONALMETRICS_DEFAULT
  • VALUE_FRACTIONALMETRICS_OFF
  • VALUE_FRACTIONALMETRICS_ON

The fractional metric hints are used for getting font metrics (FontMetrics) for text positioning. Originally, only integer values were supported. However, you can also use floating point values for more precision in positioning text. Understand however that using fractional metrics can be slower (though it typically isn't).

KEY_INTERPOLATION

The possible values are:

  • VALUE_INTERPOLATION_BICUBIC
  • VALUE_INTERPOLATION_BILINEAR
  • VALUE_INTERPOLATION_NEAREST_NEIGHBOR

Interpolation hints control affine transformations for images. (For information about affine transformations, see the September 9, 2003 Tech Tip Understanding AffineTransform). NEAREST_NEIGHBOR uses the transformed original pixel that is nearest the destination. This works fast, but results in the worst quality. BILINEAR combines the four pixels and interpolates linearly between them to determine the output pixel. This produces higher quality than the nearest neighbor choice, but is slower than nearest neighbor. BICUBIC produces the best quality, but uses a cubic interpolation equation that involves the 16 pixels surrounding the transformed location -- it is not necessarily supported on all platforms.

KEY_STROKE_CONTROL

The possible values are:

  • VALUE_STROKE_DEFAULT
  • VALUE_STROKE_NORMALIZE
  • VALUE_STROKE_PURE

This key for stroke normalization was added to J2SE 1.3 (the others are available since 1.2). Typically, drawing with a stroke translates drawn lines by a half pixel, so they are centered over a single pixel. This means that drawing a horizontal line draws a series of whole pixels, as opposed to possibly two halves (if they weren't normalized). Using VALUE_STROKE_PURE causes no normalization of path coordinates. Antialiasing settings might cause the normalization to happen to positions other than center. The exact normalization rules are unspecified.

By using the RenderingHints properly, you can control your applications to offer the best possible output, given the performance penalties you are willing to accept. If you want the fastest program, your output quality will suffer. If you don't mind giving up a few clock cycles to improve the output quality, enable the necessary rendering hints.

For additional information on using rendering hints and drawing for the Java 2 platform, see the Programmer's Guide to the Java 2D API.

.
.

CREATING CUSTOM SECURITY PERMISSIONS

The January 30, 2001 Tech Tip Controlling Package Access With Security Permissions explored how to work with a security manager and an application-specific policy file to control a program's permissions. That approach works well if you need to set permissions using a preexisting set of permissions. But what if one of the preexisting permissions doesn't suit your needs, and you need to create a custom security permission? This tip explains how to do that.

The process of working with custom permissions involves four steps:

  1. Create a new subclass of the java.security.Permission class.

  2. Prior to executing the block of code needing the permission, check to see if the code has permission.

  3. After ensuring that proper permissions are available, run the privileged code.

  4. Add the necessary new permission to the policy file for the application, then run the program.

Why would you go through the trouble of creating a new security permission? The answer is that there are only a predefined set of permissions available with the Java platform. These have predefined purposes. You can't redefine the use of something such as FilePermission for restricting access to your particular subsystem. By creating your own permission, you can restrict access to a more appropriate set of users.

Creating a new Permission class is simple, just create a subclass of java.security.Permission. Typically, when defining new permissions, you create a subclass of the java.security.BasicPermission class. That's because the class contains a lot of common code for typical needs. The BasicPermission class is abstract, although it doesn't contain any abstract methods. Your subclass defines the permission type. In addition to type, permissions typically have an associated name. As an example, for the FilePermission type, the name is the name of the file to which the permission applies. Each file name can have a different permission name. In the policy file, the same permission type is specified once for each name. In addition to type and name, permissions can carry actions. Carrying the file permission type forward, the action could be read and write. The available set of actions are specific to the permission, and is optional.

Let's create a new permission type named MyPermission, with two actions: read and write. This permission will be used to protect a high-power string reversal service, where you can only reverse strings you know about. On a more realistic level, imagine a bond price projection service where you want to limit the service to those who have paid to estimate specific bond quotes.

Start with the class definition:

   public class MyPermission 
     extends BasicPermission 
     implements Serializable {

Although BasicPermission defines the semantics of actions, it doesn't do anything with them. So, you need to serialize them yourself. Technically speaking, because BasicPermission implements Serializable, you don't need to explicitly declare that, but implementing serves as a reminder.

Permissions with actions need to be validated. Beyond the BasicPermission checks for validity of the name, there's no additional checks necessary:

   public MyPermission(String name) {
     this(name, "read");
   }

   public MyPermission(String name, String actions) {
     super(name);
     init(getMask(actions));
   }

The getMask method converts a string form of actions, such as "read, write", to an integer mask -- one bit on for each option. The basic pattern to follow here is one constant per masking option:

   private static final int NONE = 0x00;
   private static final int READ = 0x01;
   private static final int WRITE = 0x02;
   private static final int ALL = READ | WRITE;    

Then, build up the mask from the string:

 
   private static int getMask(String actions) {
     int mask = NONE;
     if (actions == null) {
       return mask;
     }

     actions = actions.toLowerCase();

     StringTokenizer st = new StringTokenizer
             (actions, ",");

     while (st.hasMoreTokens()) {
       String token = st.nextToken().trim();
       if ("read".equals(token)) {
         mask |= READ;
       } else if ("write".equals(token)) {
         mask |= WRITE;
       } else {
         throw new IllegalArgumentException(
           "Invalid permission: " + actions);
       }
     }
     return mask;
   }

The init method called in the constructor ensures that you have a valid mask, and stores it:

   protected int mask;
   private void init(int mask) {
     if ((mask & ALL) != mask) {
       throw new IllegalArgumentException
               ("Invalid actions mask");
     }
     this.mask = mask;
   }

This might seem like overkill to ensure validity, but the same check needs to happen when you deserialize the permission.

   private synchronized void readObject
           (ObjectInputStream s)
       throws IOException, ClassNotFoundException {
     s.defaultReadObject();
     init(getMask(actions));
   }

And here's the reverse for writing the object. The implementation keeps the actions variable null as long as possible.

   private String actions;
   private synchronized void writeObject
           (ObjectOutputStream s) 
       throws IOException {
     if (actions == null) {
       getActions();
     }
     s.defaultWriteObject();
   }

   public String getActions() {
     if (actions == null) {
       actions = getActions(mask);
     }
     return actions;
   }

   private static String getActions(int mask) {
     StringBuffer sb = new StringBuffer();
     boolean comma = false;

     if ((mask & READ) == READ) {
       comma = true;
       sb.append("read");
     }
     if ((mask & WRITE) == WRITE) {
       if (comma) {
         sb.append(',');
       } else {
         comma = true;
       }
       sb.append("write");
     }
     return sb.toString();
   }

As more actions are added, additional if-blocks can be added to the private static getActions method, leaving the public instance method alone.

You'll need the typical equals and hash code methods, too. Another method you'll need to implement is an implies method. The equals method checks if two permissions have the same name and mask. The implies method indicates that a permission with both read and write actions implies just the read or write action.

What follows is the complete MyPermission definition, with the last three methods defined, and a toString method:

   package project;

   import java.io.IOException;
   import java.io.ObjectInputStream;
   import java.io.ObjectOutputStream;
   import java.io.Serializable;
   import java.security.BasicPermission;
   import java.security.Permission;
   import java.util.StringTokenizer;

   public class MyPermission extends BasicPermission 
           implements Serializable {
     protected int mask;
     /**
      * No actions
      */
      private static final int NONE = 0x00;
     /**
      * Read action
      */
      private static final int READ = 0x01;
     /**
      * Write action
      */
      private static final int WRITE = 0x02;
     /**
      * All actions
      */
      private static final int ALL = READ | WRITE;
     /**
      * The actions string
      * @serial
      */
      private String actions;

     /**
      * Construct a MyPermission object. Defaults to
      * 'read' action
      * @param name The name to access
      */
     public MyPermission(String name) {
       this(name, "read");
     }

     /**
      * Construct a MyPermission
      * @param name The name to access
      * @param actions The set of actions permissible
      */
     public MyPermission(String name, String actions) {
       super(name);
       init(getMask(actions));
     }

     /**
      * Ensures valid mask into permission
      * @param mask the actions mask to use
      * @throws IllegalArgumentException 
      * if actions mask invalid
      */
      private void init(int mask) {
        if ((mask & ALL) != mask) {
          throw new IllegalArgumentException
                  ("Invalid actions mask");
        }
        this.mask = mask;
      }

     /**
      * Checks if this MyPermission object implies 
      * the specified permission. It must be an instance 
      * of MyPermission, names equal or implied by the 
      * object's name (a.b.* implies a.b.c) and the 
      * actions are a proper subset.
      * @param p the permission to check against
      *
      * @return true if the specified permission is equal 
      * or implied by this object, false otherwise.
      */
      public boolean implies(Permission p) {
        if (!super.implies(p)) {
          return false;
        } else {
          MyPermission that = (MyPermission)p;
          return ((this.mask & that.mask) == that.mask);
        }
      }

     /**
      * Checks two MyPermission objects for equality. 
      * Checks that other object is a MyPermission 
      * and has same name and set of actions.
      * @param obj the object we are testing against
      * @return true if obj is a MyPermission and has 
      * same name and actions as this MyPermission object, 
      * false otherwise.
      */
      public boolean equals(Object obj) {
        if (obj == this) {
          return true;
        }

        if (!(obj instanceof MyPermission)) {
          return false;
        }

        MyPermission that = (MyPermission)obj;

        return ((this.mask == that.mask) &&
          (this.getName().equals(that.getName())));
      }

     /**
      * Returns the hash code value for this object.
      * 
      * @return a hash code value for this object.
      */
      public int hashCode() {
        return this.getName().hashCode() + this.mask;
      }

     /**
      * Converts an actions String to an actions mask
      *
      * @param action the action string
      * @return the actions mask
      * @throws IllegualArgumentException if invalid 
      * action in actions String
      */
      private static int getMask(String actions) {
        int mask = NONE;
        if (actions == null) {
          return mask;
        }

        actions = actions.toLowerCase();

        StringTokenizer st = 
                new StringTokenizer(actions, ",");

        while (st.hasMoreTokens()) {
          String token = st.nextToken().trim();
          if ("read".equals(token)) {
            mask |= READ;
          } else if ("write".equals(token)) {
            mask |= WRITE;
          } else {
            throw new IllegalArgumentException
                    ("Invalid permission: " + actions);
          }
        }
        return mask;
      }

     /**
      * Returns the canonical string representation 
      * of the actions. The available actions are 
      * always returned in the following order: 
      * read, write
      *
      * @return the action string
      */
      public String getActions() {
        if (actions == null) {
          actions = getActions(mask);
        }
        return actions;
      }

      private static String getActions(int mask) {
        StringBuffer sb = new StringBuffer();
        boolean comma = false;

        if ((mask & READ) == READ) {
          comma = true;
          sb.append("read");
        }

        if ((mask & WRITE) == WRITE) {
          if (comma) {
            sb.append(',');
          } else {
            comma = true;
          }
          sb.append("write");
        }
        return sb.toString();
      }
       
     /**
      * Saves the state of the permission to a stream. 
      * The actions are serialized, and the superclass 
      * takes care of the name.
      *
      * @param s the ObjectOutputStream to write to
      *
      * @throws IOException if I/O errors occur while 
      * writing to the underlying OutputStream
      */
      private synchronized void writeObject
            (ObjectOutputStream s) throws IOException {
        // Write out the actions. The superclass takes 
        // care of the name call getActions to make sure 
        // actions field is initialized
        if (actions == null) {
          getActions();
        }
        s.defaultWriteObject();
      }

     /**
      * Restores the state of the permission 
      * from a stream.
      *
      * @param s the ObjectInputStream to read from
      *
      * @throws IOException if I/O errors occur while 
      * reading from the underlying InputStream
      * @throws ClassNotFoundException if the class 
      * of a serialized object could not be found
      */
      private synchronized void readObject
             (ObjectInputStream s) throws IOException, 
             ClassNotFoundException {
        // Read in the actions, then restore everything 
        // else by calling init.
        s.defaultReadObject();
        init(getMask(actions));
      }

     /**
      * Generates string representation of 
      * class information.
      *
      * @return String representation of state
      */
      public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(getClass().getName());
        sb.append("[");
        sb.append(getName());
        sb.append(",");
        sb.append(getActions());
        sb.append("]");
        return sb.toString();
      }
   }

Now that you have a new custom Permission class, what can you do with it? That's where the second piece of code comes into play. Prior to where you need to execute a block of privileged code, you need to check if you have this new permission:

   SecurityManager sm = System.getSecurityManager();
   if (sm == null) {
     throw new SecurityException
             ("Must have security manager");
   }
   sm.checkPermission(new MyPermission(name, action));

First, this code checks to make sure you are running with a security manager. If you're not running with a security manager, the code throws a security exception. The last line of code checks that for a given type and action, the current execution context is running with the necessary permission. If not, the code throws a security exception. Your program will never reach the line after this check if the execution context lacks the necessary permission.

If you don't care if you have a SecurityManager installed, you can call the checkPermission method of AccessController. This works the same way as the above check, however without a SecurityManager present, you automatically have permission for everything.

If you have the necessary permissions, you can then execute the code that requires those permissions. Continuing from the above adds two more lines after the check permission. Imagine this doing a network request to that bond pricing service instead of simply reversing a string.

    sm.checkPermission(new MyPermission(string));
    StringBuffer buffer = new StringBuffer(string);
    String reversed = buffer.reverse().toString();

The following program, PermCheck, demonstrates the use of the MyPermission class. The program checks for read access of the MyPermission class for whatever filename is passed in from the command line. If permission exists, the program reverses that string.

   import project.MyPermission;
   import java.security.*;

   public class PermCheck {
     public static void main(String args[]) {
       if (args.length != 1) {
         System.out.println("Please specify string");
         System.exit(-1);
       }
       final String string = args[0];
       SecurityManager sm = System.getSecurityManager();
       if (sm == null) {
         throw new SecurityException
                 ("Must have security manager");
       }
       sm.checkPermission(new MyPermission(string));
       StringBuffer buffer = new StringBuffer(string);
       String reversed = buffer.reverse().toString();
       System.out.println
               (string + " reversed is " + reversed);
     }
   }

You need to create a policy file to use with the PermCheck program. The following demonstrates the contents of a policy file needed to reverse the string foo. Make sure to put a semicolon after the closing bracket. Feel free to specify additional strings, or grant the permission to a specific code base. Place this grant block in a file named java.policy.

   grant {
     permission project.MyPermission "foo", "read";
   };

To run the PermCheck program with a security manager, include -Djava.security.manager on the command line. To specify a security policy file, have the java.security.policy property reference the file (the following should go on one line):

  java -Djava.security.manager 
     -Djava.security.policy=java.policy 
     PermCheck

If you run the program, you should see a prompt for a string:

   Please specify filename

Try specifying a string that doesn't exist. For example, specify bar:

   java -Djava.security.manager 
     -Djava.security.policy=java.policy 
     PermCheck bar

In response, you should see the following exception:

   Exception in thread "main"
   java.security.AccessControlException: 
       access denied project.MyPermission[bar,read]
    at java.security.AccessControlContext.checkPermission(
       AccessControlContext.java:269)
    at java.security.AccessController.checkPermission(
       AccessController.java:401)
    at java.lang.SecurityManager.checkPermission(
       SecurityManager.java:524)
    at PermCheck.main(PermCheck.java:15)

Next, specify a string that is provided in the java.policy file:

   java -Djava.security.manager 
     -Djava.security.policy=java.policy 
     PermCheck foo

When you run the program you should see the following:

  foo reversed is oof

All these steps might seem a little excessive. However, they do the job well. Because the privileged operation only returns a Reader stream, it hides the fact that this represents a file. If the implementation changed to get the contents from a socket, the program wouldn't need any additional permissions. Your policy file didn't have FilePermission before, and wouldn't need SocketPermission if the implementation changed. That's the reason why you want to create your own custom security permission.

For additional information on creating Java security policies and custom permissions, see the lesson Implementing Your Own Permission in The Java Tutorial.

.
.
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.
.
.

IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html

Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.

Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/developer/JDCTechTips/index.html


Copyright 2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.