1 /*
   2  * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.imageio.spi;
  27 
  28 import java.io.IOException;
  29 import java.lang.reflect.Constructor;
  30 import java.lang.reflect.Method;
  31 import java.lang.reflect.Module;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 import java.util.Arrays;
  35 import java.util.Iterator;
  36 import javax.imageio.ImageReader;
  37 import javax.imageio.metadata.IIOMetadata;
  38 import javax.imageio.metadata.IIOMetadataFormat;
  39 import javax.imageio.metadata.IIOMetadataFormatImpl;
  40 import javax.imageio.stream.ImageInputStream;
  41 
  42 /**
  43  * A superclass containing instance variables and methods common to
  44  * {@code ImageReaderSpi} and {@code ImageWriterSpi}.
  45  *
  46  * @see IIORegistry
  47  * @see ImageReaderSpi
  48  * @see ImageWriterSpi
  49  *
  50  */
  51 public abstract class ImageReaderWriterSpi extends IIOServiceProvider {
  52 
  53     /**
  54      * An array of strings to be returned from
  55      * {@code getFormatNames}, initially {@code null}.
  56      * Constructors should set this to a non-{@code null} value.
  57      */
  58     protected String[] names = null;
  59 
  60     /**
  61      * An array of strings to be returned from
  62      * {@code getFileSuffixes}, initially {@code null}.
  63      */
  64     protected String[] suffixes = null;
  65 
  66     /**
  67      * An array of strings to be returned from
  68      * {@code getMIMETypes}, initially {@code null}.
  69      */
  70     protected String[] MIMETypes = null;
  71 
  72     /**
  73      * A {@code String} containing the name of the associated
  74      * plug-in class, initially {@code null}.
  75      */
  76     protected String pluginClassName = null;
  77 
  78     /**
  79      * A boolean indicating whether this plug-in supports the
  80      * standard metadata format for stream metadata, initially
  81      * {@code false}.
  82      */
  83     protected boolean supportsStandardStreamMetadataFormat = false;
  84 
  85     /**
  86      * A {@code String} containing the name of the native stream
  87      * metadata format supported by this plug-in, initially
  88      * {@code null}.
  89      */
  90     protected String nativeStreamMetadataFormatName = null;
  91 
  92     /**
  93      * A {@code String} containing the class name of the native
  94      * stream metadata format supported by this plug-in, initially
  95      * {@code null}.
  96      */
  97     protected String nativeStreamMetadataFormatClassName = null;
  98 
  99     /**
 100      * An array of {@code String}s containing the names of any
 101      * additional stream metadata formats supported by this plug-in,
 102      * initially {@code null}.
 103      */
 104     protected String[] extraStreamMetadataFormatNames = null;
 105 
 106     /**
 107      * An array of {@code String}s containing the class names of
 108      * any additional stream metadata formats supported by this plug-in,
 109      * initially {@code null}.
 110      */
 111     protected String[] extraStreamMetadataFormatClassNames = null;
 112 
 113     /**
 114      * A boolean indicating whether this plug-in supports the
 115      * standard metadata format for image metadata, initially
 116      * {@code false}.
 117      */
 118     protected boolean supportsStandardImageMetadataFormat = false;
 119 
 120     /**
 121      * A {@code String} containing the name of the
 122      * native stream metadata format supported by this plug-in,
 123      * initially {@code null}.
 124      */
 125     protected String nativeImageMetadataFormatName = null;
 126 
 127     /**
 128      * A {@code String} containing the class name of the
 129      * native stream metadata format supported by this plug-in,
 130      * initially {@code null}.
 131      */
 132     protected String nativeImageMetadataFormatClassName = null;
 133 
 134     /**
 135      * An array of {@code String}s containing the names of any
 136      * additional image metadata formats supported by this plug-in,
 137      * initially {@code null}.
 138      */
 139     protected String[] extraImageMetadataFormatNames = null;
 140 
 141     /**
 142      * An array of {@code String}s containing the class names of
 143      * any additional image metadata formats supported by this
 144      * plug-in, initially {@code null}.
 145      */
 146     protected String[] extraImageMetadataFormatClassNames = null;
 147 
 148     /**
 149      * Constructs an {@code ImageReaderWriterSpi} with a given
 150      * set of values.
 151      *
 152      * @param vendorName the vendor name, as a non-{@code null}
 153      * {@code String}.
 154      * @param version a version identifier, as a non-{@code null}
 155      * {@code String}.
 156      * @param names a non-{@code null} array of
 157      * {@code String}s indicating the format names.  At least one
 158      * entry must be present.
 159      * @param suffixes an array of {@code String}s indicating the
 160      * common file suffixes.  If no suffixes are defined,
 161      * {@code null} should be supplied.  An array of length 0
 162      * will be normalized to {@code null}.
 163      * @param MIMETypes an array of {@code String}s indicating
 164      * the format's MIME types.  If no MIME types are defined,
 165      * {@code null} should be supplied.  An array of length 0
 166      * will be normalized to {@code null}.
 167      * @param pluginClassName the fully-qualified name of the
 168      * associated {@code ImageReader} or {@code ImageWriter}
 169      * class, as a non-{@code null String}.
 170      * @param supportsStandardStreamMetadataFormat a
 171      * {@code boolean} that indicates whether a stream metadata
 172      * object can use trees described by the standard metadata format.
 173      * @param nativeStreamMetadataFormatName a
 174      * {@code String}, or {@code null}, to be returned from
 175      * {@code getNativeStreamMetadataFormatName}.
 176      * @param nativeStreamMetadataFormatClassName a
 177      * {@code String}, or {@code null}, to be used to instantiate
 178      * a metadata format object to be returned from
 179      * {@code getNativeStreamMetadataFormat}.
 180      * @param extraStreamMetadataFormatNames an array of
 181      * {@code String}s, or {@code null}, to be returned from
 182      * {@code getExtraStreamMetadataFormatNames}.  An array of length
 183      * 0 is normalized to {@code null}.
 184      * @param extraStreamMetadataFormatClassNames an array of
 185      * {@code String}s, or {@code null}, to be used to instantiate
 186      * a metadata format object to be returned from
 187      * {@code getStreamMetadataFormat}.  An array of length
 188      * 0 is normalized to {@code null}.
 189      * @param supportsStandardImageMetadataFormat a
 190      * {@code boolean} that indicates whether an image metadata
 191      * object can use trees described by the standard metadata format.
 192      * @param nativeImageMetadataFormatName a
 193      * {@code String}, or {@code null}, to be returned from
 194      * {@code getNativeImageMetadataFormatName}.
 195      * @param nativeImageMetadataFormatClassName a
 196      * {@code String}, or {@code null}, to be used to instantiate
 197      * a metadata format object to be returned from
 198      * {@code getNativeImageMetadataFormat}.
 199      * @param extraImageMetadataFormatNames an array of
 200      * {@code String}s to be returned from
 201      * {@code getExtraImageMetadataFormatNames}.  An array of length 0
 202      * is normalized to {@code null}.
 203      * @param extraImageMetadataFormatClassNames an array of
 204      * {@code String}s, or {@code null}, to be used to instantiate
 205      * a metadata format object to be returned from
 206      * {@code getImageMetadataFormat}.  An array of length
 207      * 0 is normalized to {@code null}.
 208      *
 209      * @exception IllegalArgumentException if {@code vendorName}
 210      * is {@code null}.
 211      * @exception IllegalArgumentException if {@code version}
 212      * is {@code null}.
 213      * @exception IllegalArgumentException if {@code names}
 214      * is {@code null} or has length 0.
 215      * @exception IllegalArgumentException if {@code pluginClassName}
 216      * is {@code null}.
 217      */
 218     public ImageReaderWriterSpi(String vendorName,
 219                                 String version,
 220                                 String[] names,
 221                                 String[] suffixes,
 222                                 String[] MIMETypes,
 223                                 String pluginClassName,
 224                                 boolean supportsStandardStreamMetadataFormat,
 225                                 String nativeStreamMetadataFormatName,
 226                                 String nativeStreamMetadataFormatClassName,
 227                                 String[] extraStreamMetadataFormatNames,
 228                                 String[] extraStreamMetadataFormatClassNames,
 229                                 boolean supportsStandardImageMetadataFormat,
 230                                 String nativeImageMetadataFormatName,
 231                                 String nativeImageMetadataFormatClassName,
 232                                 String[] extraImageMetadataFormatNames,
 233                                 String[] extraImageMetadataFormatClassNames) {
 234         super(vendorName, version);
 235         if (names == null) {
 236             throw new IllegalArgumentException("names == null!");
 237         }
 238         if (names.length == 0) {
 239             throw new IllegalArgumentException("names.length == 0!");
 240         }
 241         if (pluginClassName == null) {
 242             throw new IllegalArgumentException("pluginClassName == null!");
 243         }
 244 
 245         this.names = names.clone();
 246         // If length == 0, leave it null
 247         if (suffixes != null && suffixes.length > 0) {
 248             this.suffixes = suffixes.clone();
 249         }
 250         // If length == 0, leave it null
 251         if (MIMETypes != null && MIMETypes.length > 0) {
 252             this.MIMETypes = MIMETypes.clone();
 253         }
 254         this.pluginClassName = pluginClassName;
 255 
 256         this.supportsStandardStreamMetadataFormat =
 257             supportsStandardStreamMetadataFormat;
 258         this.nativeStreamMetadataFormatName = nativeStreamMetadataFormatName;
 259         this.nativeStreamMetadataFormatClassName =
 260             nativeStreamMetadataFormatClassName;
 261         // If length == 0, leave it null
 262         if (extraStreamMetadataFormatNames != null &&
 263             extraStreamMetadataFormatNames.length > 0) {
 264             this.extraStreamMetadataFormatNames =
 265                 extraStreamMetadataFormatNames.clone();
 266         }
 267         // If length == 0, leave it null
 268         if (extraStreamMetadataFormatClassNames != null &&
 269             extraStreamMetadataFormatClassNames.length > 0) {
 270             this.extraStreamMetadataFormatClassNames =
 271                 extraStreamMetadataFormatClassNames.clone();
 272         }
 273         this.supportsStandardImageMetadataFormat =
 274             supportsStandardImageMetadataFormat;
 275         this.nativeImageMetadataFormatName = nativeImageMetadataFormatName;
 276         this.nativeImageMetadataFormatClassName =
 277             nativeImageMetadataFormatClassName;
 278         // If length == 0, leave it null
 279         if (extraImageMetadataFormatNames != null &&
 280             extraImageMetadataFormatNames.length > 0) {
 281             this.extraImageMetadataFormatNames =
 282                 extraImageMetadataFormatNames.clone();
 283         }
 284         // If length == 0, leave it null
 285         if (extraImageMetadataFormatClassNames != null &&
 286             extraImageMetadataFormatClassNames.length > 0) {
 287             this.extraImageMetadataFormatClassNames =
 288                 extraImageMetadataFormatClassNames.clone();
 289         }
 290     }
 291 
 292     /**
 293      * Constructs a blank {@code ImageReaderWriterSpi}.  It is up
 294      * to the subclass to initialize instance variables and/or
 295      * override method implementations in order to provide working
 296      * versions of all methods.
 297      */
 298     public ImageReaderWriterSpi() {
 299     }
 300 
 301     /**
 302      * Returns an array of {@code String}s containing
 303      * human-readable names for the formats that are generally usable
 304      * by the {@code ImageReader} or {@code ImageWriter}
 305      * implementation associated with this service provider.  For
 306      * example, a single {@code ImageReader} might be able to
 307      * process both PBM and PNM files.
 308      *
 309      * @return a non-{@code null} array of {@code String}s
 310      * or length at least 1 containing informal format names
 311      * associated with this reader or writer.
 312      */
 313     public String[] getFormatNames() {
 314         return names.clone();
 315     }
 316 
 317     /**
 318      * Returns an array of {@code String}s containing a list of
 319      * file suffixes associated with the formats that are generally
 320      * usable by the {@code ImageReader} or
 321      * {@code ImageWriter} implementation associated with this
 322      * service provider.  For example, a single
 323      * {@code ImageReader} might be able to process files with
 324      * '.pbm' and '.pnm' suffixes, or both '.jpg' and '.jpeg'
 325      * suffixes.  If there are no known file suffixes,
 326      * {@code null} will be returned.
 327      *
 328      * <p> Returning a particular suffix does not guarantee that files
 329      * with that suffix can be processed; it merely indicates that it
 330      * may be worthwhile attempting to decode or encode such files
 331      * using this service provider.
 332      *
 333      * @return an array of {@code String}s or length at least 1
 334      * containing common file suffixes associated with this reader or
 335      * writer, or {@code null}.
 336      */
 337     public String[] getFileSuffixes() {
 338         return suffixes == null ? null : suffixes.clone();
 339     }
 340 
 341     /**
 342      * Returns an array of {@code String}s containing a list of
 343      * MIME types associated with the formats that are generally
 344      * usable by the {@code ImageReader} or
 345      * {@code ImageWriter} implementation associated with this
 346      * service provider.
 347      *
 348      * <p> Ideally, only a single MIME type would be required in order
 349      * to describe a particular format.  However, for several reasons
 350      * it is necessary to associate a list of types with each service
 351      * provider.  First, many common image file formats do not have
 352      * standard MIME types, so a list of commonly used unofficial
 353      * names will be required, such as {@code image/x-pbm} and
 354      * {@code image/x-portable-bitmap}.  Some file formats have
 355      * official MIME types but may sometimes be referred to using
 356      * their previous unofficial designations, such as
 357      * {@code image/x-png} instead of the official
 358      * {@code image/png}.  Finally, a single service provider may
 359      * be capable of parsing multiple distinct types from the MIME
 360      * point of view, for example {@code image/x-xbitmap} and
 361      * {@code image/x-xpixmap}.
 362      *
 363      * <p> Returning a particular MIME type does not guarantee that
 364      * files claiming to be of that type can be processed; it merely
 365      * indicates that it may be worthwhile attempting to decode or
 366      * encode such files using this service provider.
 367      *
 368      * @return an array of {@code String}s or length at least 1
 369      * containing MIME types associated with this reader or writer, or
 370      * {@code null}.
 371      */
 372     public String[] getMIMETypes() {
 373         return MIMETypes == null ? null : MIMETypes.clone();
 374     }
 375 
 376     /**
 377      * Returns the fully-qualified class name of the
 378      * {@code ImageReader} or {@code ImageWriter} plug-in
 379      * associated with this service provider.
 380      *
 381      * @return the class name, as a non-{@code null}
 382      * {@code String}.
 383      */
 384     public String getPluginClassName() {
 385         return pluginClassName;
 386     }
 387 
 388     /**
 389      * Returns {@code true} if the standard metadata format is
 390      * among the document formats recognized by the
 391      * {@code getAsTree} and {@code setFromTree} methods on
 392      * the stream metadata objects produced or consumed by this
 393      * plug-in.
 394      *
 395      * @return {@code true} if the standard format is supported
 396      * for stream metadata.
 397      */
 398     public boolean isStandardStreamMetadataFormatSupported() {
 399         return supportsStandardStreamMetadataFormat;
 400     }
 401 
 402     /**
 403      * Returns the name of the "native" stream metadata format for
 404      * this plug-in, which typically allows for lossless encoding and
 405      * transmission of the stream metadata stored in the format handled by
 406      * this plug-in.  If no such format is supported,
 407      * {@code null} will be returned.
 408      *
 409      * <p> The default implementation returns the
 410      * {@code nativeStreamMetadataFormatName} instance variable,
 411      * which is typically set by the constructor.
 412      *
 413      * @return the name of the native stream metadata format, or
 414      * {@code null}.
 415      *
 416      */
 417     public String getNativeStreamMetadataFormatName() {
 418         return nativeStreamMetadataFormatName;
 419     }
 420 
 421     /**
 422      * Returns an array of {@code String}s containing the names
 423      * of additional document formats, other than the native and
 424      * standard formats, recognized by the
 425      * {@code getAsTree} and {@code setFromTree} methods on
 426      * the stream metadata objects produced or consumed by this
 427      * plug-in.
 428      *
 429      * <p> If the plug-in does not handle metadata, null should be
 430      * returned.
 431      *
 432      * <p> The set of formats may differ according to the particular
 433      * images being read or written; this method should indicate all
 434      * the additional formats supported by the plug-in under any
 435      * circumstances.
 436      *
 437      * <p> The default implementation returns a clone of the
 438      * {@code extraStreamMetadataFormatNames} instance variable,
 439      * which is typically set by the constructor.
 440      *
 441      * @return an array of {@code String}s, or null.
 442      *
 443      * @see IIOMetadata#getMetadataFormatNames
 444      * @see #getExtraImageMetadataFormatNames
 445      * @see #getNativeStreamMetadataFormatName
 446      */
 447     public String[] getExtraStreamMetadataFormatNames() {
 448         return extraStreamMetadataFormatNames == null ?
 449             null : extraStreamMetadataFormatNames.clone();
 450     }
 451 
 452     /**
 453      * Returns {@code true} if the standard metadata format is
 454      * among the document formats recognized by the
 455      * {@code getAsTree} and {@code setFromTree} methods on
 456      * the image metadata objects produced or consumed by this
 457      * plug-in.
 458      *
 459      * @return {@code true} if the standard format is supported
 460      * for image metadata.
 461      */
 462     public boolean isStandardImageMetadataFormatSupported() {
 463         return supportsStandardImageMetadataFormat;
 464     }
 465 
 466     /**
 467      * Returns the name of the "native" image metadata format for
 468      * this plug-in, which typically allows for lossless encoding and
 469      * transmission of the image metadata stored in the format handled by
 470      * this plug-in.  If no such format is supported,
 471      * {@code null} will be returned.
 472      *
 473      * <p> The default implementation returns the
 474      * {@code nativeImageMetadataFormatName} instance variable,
 475      * which is typically set by the constructor.
 476      *
 477      * @return the name of the native image metadata format, or
 478      * {@code null}.
 479      *
 480      * @see #getExtraImageMetadataFormatNames
 481      */
 482     public String getNativeImageMetadataFormatName() {
 483         return nativeImageMetadataFormatName;
 484     }
 485 
 486     /**
 487      * Returns an array of {@code String}s containing the names
 488      * of additional document formats, other than the native and
 489      * standard formats, recognized by the
 490      * {@code getAsTree} and {@code setFromTree} methods on
 491      * the image metadata objects produced or consumed by this
 492      * plug-in.
 493      *
 494      * <p> If the plug-in does not handle image metadata, null should
 495      * be returned.
 496      *
 497      * <p> The set of formats may differ according to the particular
 498      * images being read or written; this method should indicate all
 499      * the additional formats supported by the plug-in under any circumstances.
 500      *
 501      * <p> The default implementation returns a clone of the
 502      * {@code extraImageMetadataFormatNames} instance variable,
 503      * which is typically set by the constructor.
 504      *
 505      * @return an array of {@code String}s, or null.
 506      *
 507      * @see IIOMetadata#getMetadataFormatNames
 508      * @see #getExtraStreamMetadataFormatNames
 509      * @see #getNativeImageMetadataFormatName
 510      */
 511     public String[] getExtraImageMetadataFormatNames() {
 512         return extraImageMetadataFormatNames == null ?
 513             null : extraImageMetadataFormatNames.clone();
 514     }
 515 
 516     /**
 517      * Returns an {@code IIOMetadataFormat} object describing the
 518      * given stream metadata format, or {@code null} if no
 519      * description is available.  The supplied name must be the native
 520      * stream metadata format name, the standard metadata format name,
 521      * or one of those returned by
 522      * {@code getExtraStreamMetadataFormatNames}.
 523      *
 524      * @param formatName the desired stream metadata format.
 525      *
 526      * @return an {@code IIOMetadataFormat} object.
 527      *
 528      * @exception IllegalArgumentException if {@code formatName}
 529      * is {@code null} or is not a supported name.
 530      */
 531     public IIOMetadataFormat getStreamMetadataFormat(String formatName) {
 532         return getMetadataFormat(formatName,
 533                                  supportsStandardStreamMetadataFormat,
 534                                  nativeStreamMetadataFormatName,
 535                                  nativeStreamMetadataFormatClassName,
 536                                  extraStreamMetadataFormatNames,
 537                                  extraStreamMetadataFormatClassNames);
 538     }
 539 
 540     /**
 541      * Returns an {@code IIOMetadataFormat} object describing the
 542      * given image metadata format, or {@code null} if no
 543      * description is available.  The supplied name must be the native
 544      * image metadata format name, the standard metadata format name,
 545      * or one of those returned by
 546      * {@code getExtraImageMetadataFormatNames}.
 547      *
 548      * @param formatName the desired image metadata format.
 549      *
 550      * @return an {@code IIOMetadataFormat} object.
 551      *
 552      * @exception IllegalArgumentException if {@code formatName}
 553      * is {@code null} or is not a supported name.
 554      */
 555     public IIOMetadataFormat getImageMetadataFormat(String formatName) {
 556         return getMetadataFormat(formatName,
 557                                  supportsStandardImageMetadataFormat,
 558                                  nativeImageMetadataFormatName,
 559                                  nativeImageMetadataFormatClassName,
 560                                  extraImageMetadataFormatNames,
 561                                  extraImageMetadataFormatClassNames);
 562     }
 563 
 564     private IIOMetadataFormat getMetadataFormat(String formatName,
 565                                                 boolean supportsStandard,
 566                                                 String nativeName,
 567                                                 String nativeClassName,
 568                                                 String [] extraNames,
 569                                                 String [] extraClassNames) {
 570         if (formatName == null) {
 571             throw new IllegalArgumentException("formatName == null!");
 572         }
 573         if (supportsStandard && formatName.equals
 574                 (IIOMetadataFormatImpl.standardMetadataFormatName)) {
 575 
 576             return IIOMetadataFormatImpl.getStandardFormatInstance();
 577         }
 578         String formatClassName = null;
 579         if (formatName.equals(nativeName)) {
 580             formatClassName = nativeClassName;
 581         } else if (extraNames != null) {
 582             for (int i = 0; i < extraNames.length; i++) {
 583                 if (formatName.equals(extraNames[i])) {
 584                     formatClassName = extraClassNames[i];
 585                     break;  // out of for
 586                 }
 587             }
 588         }
 589         if (formatClassName == null) {
 590             throw new IllegalArgumentException("Unsupported format name");
 591         }
 592         try {
 593             // Try to load from the same location as the module of the SPI
 594             final String className = formatClassName;
 595             PrivilegedAction<Class<?>> pa = () -> { return getMetadataFormatClass(className); };
 596             Class<?> cls = AccessController.doPrivileged(pa);
 597             Method meth = cls.getMethod("getInstance");
 598             return (IIOMetadataFormat) meth.invoke(null);
 599         } catch (Exception e) {
 600             RuntimeException ex =
 601                 new IllegalStateException ("Can't obtain format");
 602             ex.initCause(e);
 603             throw ex;
 604         }
 605     }
 606 
 607     // If updating this method also see the same in IIOMetadata.java
 608     private Class<?> getMetadataFormatClass(String formatClassName) {
 609         Module thisModule = ImageReaderWriterSpi.class.getModule();
 610         Module targetModule = this.getClass().getModule();
 611         Class<?> c = null;
 612         try {
 613             ClassLoader cl = this.getClass().getClassLoader();
 614             c = Class.forName(formatClassName, false, cl);
 615             if (!IIOMetadataFormat.class.isAssignableFrom(c)) {
 616                 return null;
 617             }
 618         } catch (ClassNotFoundException e) {
 619         }
 620         if (thisModule.equals(targetModule) || c == null) {
 621             return c;
 622         }
 623         if (targetModule.isNamed()) {
 624             int i = formatClassName.lastIndexOf(".");
 625             String pn = i > 0 ? formatClassName.substring(0, i) : "";
 626             if (!targetModule.isExported(pn, thisModule)) {
 627                 throw new IllegalStateException("Class " +  formatClassName +
 628                   " in named module must be exported to java.desktop module.");
 629             }
 630         }
 631         return c;
 632     }
 633 }