Coverage report

| %line | %branch
--- | --- | ---

org.apache.commons.jelly.impl.TagScript

67%

84%

1

     /*

2

      * Copyright 2002,2004 The Apache Software Foundation.

3

      *

4

      * Licensed under the Apache License, Version 2.0 (the "License");

5

      * you may not use this file except in compliance with the License.

6

      * You may obtain a copy of the License at

7

      *

8

      *      http://www.apache.org/licenses/LICENSE-2.0

9

      *

10

      * Unless required by applicable law or agreed to in writing, software

11

      * distributed under the License is distributed on an "AS IS" BASIS,

12

      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

13

      * See the License for the specific language governing permissions and

14

      * limitations under the License.

15

      */

16

     package org.apache.commons.jelly.impl;

17

18

     import java.io.IOException;

19

     import java.lang.reflect.InvocationTargetException;

20

     import java.net.MalformedURLException;

21

     import java.net.URL;

22

     import java.util.Collections;

23

     import java.util.Hashtable;

24

     import java.util.Iterator;

25

     import java.util.Map;

26

     import java.util.WeakHashMap;

27

28

     import org.apache.commons.beanutils.ConvertingWrapDynaBean;

29

     import org.apache.commons.beanutils.ConvertUtils;

30

     import org.apache.commons.beanutils.DynaBean;

31

     import org.apache.commons.beanutils.DynaProperty;

32

33

     import org.apache.commons.jelly.CompilableTag;

34

     import org.apache.commons.jelly.JellyContext;

35

     import org.apache.commons.jelly.JellyException;

36

     import org.apache.commons.jelly.JellyTagException;

37

     import org.apache.commons.jelly.DynaTag;

38

     import org.apache.commons.jelly.LocationAware;

39

     import org.apache.commons.jelly.NamespaceAwareTag;

40

     import org.apache.commons.jelly.Script;

41

     import org.apache.commons.jelly.Tag;

42

     import org.apache.commons.jelly.XMLOutput;

43

     import org.apache.commons.jelly.expression.Expression;

44

45

     import org.apache.commons.logging.Log;

46

     import org.apache.commons.logging.LogFactory;

47

48

     import org.xml.sax.Attributes;

49

     import org.xml.sax.Locator;

50

     import org.xml.sax.SAXException;

51

52

     /**

53

      * <p><code>TagScript</code> is a Script that evaluates a custom tag.</p>

54

      *

55

      * <b>Note</b> that this class should be re-entrant and used

56

      * concurrently by multiple threads.

57

      *

58

      * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>

59

      * @version $Revision: 165507 $

60

      */

61

     public class TagScript implements Script {

62

63

         /** The Log to which logging calls will be made. */

64

1001

         private static final Log log = LogFactory.getLog(TagScript.class);

65

66

67

         /** The attribute expressions that are created */

68

19968

         protected Map attributes = new Hashtable();

69

70

         /** the optional namespaces Map of prefix -> URI of this single Tag */

71

         private Map tagNamespacesMap;

72

73

         /**

74

          * The optional namespace context mapping all prefixes -> URIs in scope

75

          * at the point this tag is used.

76

          * This Map is only created lazily if it is required by the NamespaceAwareTag.

77

          */

78

         private Map namespaceContext;

79

80

         /** the Jelly file which caused the problem */

81

         private String fileName;

82

83

         /** the qualified element name which caused the problem */

84

         private String elementName;

85

86

         /** the local (non-namespaced) tag name */

87

         private String localName;

88

89

         /** the line number of the tag */

90

19968

         private int lineNumber = -1;

91

92

         /** the column number of the tag */

93

19968

         private int columnNumber = -1;

94

95

         /** the factory of Tag instances */

96

         private TagFactory tagFactory;

97

98

         /** the body script used for this tag */

99

         private Script tagBody;

100

101

         /** the parent TagScript */

102

         private TagScript parent;

103

104

         /** the SAX attributes */

105

         private Attributes saxAttributes;

106

107

         /** the url of the script when parsed */

108

19968

         private URL scriptURL = null;

109

110

         /** A synchronized WeakHashMap from the current Thread (key) to a Tag object (value).

111

          */

112

19968

         private Map threadLocalTagCache = Collections.synchronizedMap(new WeakHashMap());

113

114

         /**

115

          * @return a new TagScript based on whether

116

          * the given Tag class is a bean tag or DynaTag

117

          */

118

         public static TagScript newInstance(Class tagClass) {

119

19721

             TagFactory factory = new DefaultTagFactory(tagClass);

120

19721

             return new TagScript(factory);

121

         }

122

123

0

         public TagScript() {

124

0

         }

125

126

19968

         public TagScript(TagFactory tagFactory) {

127

19968

             this.tagFactory = tagFactory;

128

19968

         }

129

130

         public String toString() {

131

13

             return super.toString() + "[tag=" + elementName + ";at=" + lineNumber + ":" + columnNumber + "]";

132

         }

133

134

         /**

135

          * Compiles the tags body

136

          */

137

         public Script compile() throws JellyException {

138

19903

             if (tagBody != null) {

139

19903

                 tagBody = tagBody.compile();

140

             }

141

19903

             return this;

142

         }

143

144

         /**

145

          * Sets the optional namespaces prefix -> URI map of

146

          * the namespaces attached to this Tag

147

          */

148

         public void setTagNamespacesMap(Map tagNamespacesMap) {

149

             // lets check that this is a thread-safe map

150

1040

             if ( ! (tagNamespacesMap instanceof Hashtable) ) {

151

1040

                 tagNamespacesMap = new Hashtable( tagNamespacesMap );

152

             }

153

1040

             this.tagNamespacesMap = tagNamespacesMap;

154

1040

         }

155

156

         /**

157

          * Configures this TagScript from the SAX Locator, setting the column

158

          * and line numbers

159

          */

160

         public void setLocator(Locator locator) {

161

19968

             setLineNumber( locator.getLineNumber() );

162

19968

             setColumnNumber( locator.getColumnNumber() );

163

19968

         }

164

165

166

         /** Add an initialization attribute for the tag.

167

          * This method must be called after the setTag() method

168

          */

169

         public void addAttribute(String name, Expression expression) {

170

26130

             if (log.isDebugEnabled()) {

171

0

                 log.debug("adding attribute name: " + name + " expression: " + expression);

172

             }

173

26130

             attributes.put(name, expression);

174

26130

         }

175

176

         /**

177

          * Strips off the name of a script to create a new context URL

178

          * FIXME: Copied from JellyContext

179

          */

180

         private URL getJellyContextURL(URL url) throws MalformedURLException {

181

19864

             String text = url.toString();

182

19864

             int idx = text.lastIndexOf('/');

183

19864

             text = text.substring(0, idx + 1);

184

19864

             return new URL(text);

185

         }

186

187

         // Script interface

188

         //-------------------------------------------------------------------------

189

190

         /** Evaluates the body of a tag */

191

         public void run(JellyContext context, XMLOutput output) throws JellyTagException {

192

12207

             URL rootURL = context.getRootURL();

193

12207

             URL currentURL = context.getCurrentURL();

194

             try {

195

12207

                 Tag tag = getTag(context);

196

12207

                 if ( tag == null ) {

197

                     return;

198

                 }

199

12207

                 tag.setContext(context);

200

12207

                 setContextURLs(context);

201

202

12207

                 if ( tag instanceof DynaTag ) {

203

117

                     DynaTag dynaTag = (DynaTag) tag;

204

205

                     // ### probably compiling this to 2 arrays might be quicker and smaller

206

117

                     for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {

207

442

                         Map.Entry entry = (Map.Entry) iter.next();

208

442

                         String name = (String) entry.getKey();

209

442

                         Expression expression = (Expression) entry.getValue();

210

211

442

                         Class type = dynaTag.getAttributeType(name);

212

442

                         Object value = null;

213

442

                         if (type != null && type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {

214

0

                             value = expression;

215

0

                         }

216

                         else {

217

442

                             value = expression.evaluateRecurse(context);

218

                         }

219

442

                         dynaTag.setAttribute(name, value);

220

442

                     }

221

117

                 }

222

                 else {

223

                     // treat the tag as a bean

224

12090

                     DynaBean dynaBean = new ConvertingWrapDynaBean( tag );

225

12090

                     for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {

226

15782

                         Map.Entry entry = (Map.Entry) iter.next();

227

15782

                         String name = (String) entry.getKey();

228

15782

                         Expression expression = (Expression) entry.getValue();

229

230

15782

                         DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name);

231

15782

                         if (property == null) {

232

0

                             throw new JellyException("This tag does not understand the '" + name + "' attribute" );

233

                         }

234

15782

                         Class type = property.getType();

235

236

15782

                         Object value = null;

237

15782

                         if (type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {

238

8723

                             value = expression;

239

8723

                         }

240

                         else {

241

7059

                             value = expression.evaluateRecurse(context);

242

                         }

243

15782

                         dynaBean.set(name, value);

244

15782

                     }

245

                 }

246

247

12207

                 tag.doTag(output);

248

11570

                 if (output != null) {

249

11570

                     output.flush();

250

                 }

251

             }

252

637

             catch (JellyTagException e) {

253

637

                 handleException(e);

254

0

             } catch (JellyException e) {

255

0

                 handleException(e);

256

0

             } catch (IOException e) {

257

0

                 handleException(e);

258

0

             } catch (RuntimeException e) {

259

0

                 handleException(e);

260

             }

261

0

             catch (Error e) {

262

                /*

263

                 * Not sure if we should be converting errors to exceptions,

264

                 * but not trivial to remove because JUnit tags throw

265

                 * Errors in the normal course of operation.  Hmm...

266

                 */

267

0

                 handleException(e);

268

             } finally {

269

12207

                 context.setRootURL(rootURL);

270

12207

                 context.setCurrentURL(currentURL);

271

12207

             }

272

273

11570

         }

274

275

         /**

276

          * Set the context's root and current URL if not present

277

          * @param context

278

          * @throws JellyTagException

279

          */

280

         protected void setContextURLs(JellyContext context) throws JellyTagException {

281

12363

             if ((context.getCurrentURL() == null || context.getRootURL() == <span class="keyword">null) && scriptURL != <span class="keyword">null)

282

             {

283

806

                 if (context.getRootURL() == null) context.setRootURL(scriptURL);

284

806

                 if (context.getCurrentURL() == null) context.setCurrentURL(scriptURL);

285

             }

286

12363

         }

287

288

         // Properties

289

         //-------------------------------------------------------------------------

290

291

         /**

292

          * @return the tag to be evaluated, creating it lazily if required.

293

          */

294

         public Tag getTag(JellyContext context) throws JellyException {

295

21034

             Thread t = Thread.currentThread();

296

21034

             Tag tag = (Tag) threadLocalTagCache.get(t);

297

21034

             if ( tag == null ) {

298

9646

                 tag = createTag();

299

9646

                 if ( tag != null ) {

300

9646

                     threadLocalTagCache.put(t,tag);

301

9646

                     configureTag(tag,context);

302

                 }

303

             }

304

21034

             return tag;

305

         }

306

307

         /**

308

          * Returns the Factory of Tag instances.

309

          * @return the factory

310

          */

311

         public TagFactory getTagFactory() {

312

0

             return tagFactory;

313

         }

314

315

         /**

316

          * Sets the Factory of Tag instances.

317

          * @param tagFactory The factory to set

318

          */

319

         public void setTagFactory(TagFactory tagFactory) {

320

0

             this.tagFactory = tagFactory;

321

0

         }

322

323

         /**

324

          * Returns the parent.

325

          * @return TagScript

326

          */

327

         public TagScript getParent() {

328

0

             return parent;

329

         }

330

331

         /**

332

          * Returns the tagBody.

333

          * @return Script

334

          */

335

         public Script getTagBody() {

336

0

             return tagBody;

337

         }

338

339

         /**

340

          * Sets the parent.

341

          * @param parent The parent to set

342

          */

343

         public void setParent(TagScript parent) {

344

19968

             this.parent = parent;

345

19968

         }

346

347

         /**

348

          * Sets the tagBody.

349

          * @param tagBody The tagBody to set

350

          */

351

         public void setTagBody(Script tagBody) {

352

19968

             this.tagBody = tagBody;

353

19968

         }

354

355

         /**

356

          * @return the Jelly file which caused the problem

357

          */

358

         public String getFileName() {

359

0

             return fileName;

360

         }

361

362

         /**

363

          * Sets the Jelly file which caused the problem

364

          */

365

         public void setFileName(String fileName) {

366

19968

             this.fileName = fileName;

367

             try

368

             {

369

19968

                 this.scriptURL = getJellyContextURL(new URL(fileName));

370

104

             } catch (MalformedURLException e) {

371

104

                 log.debug("error setting script url", e);

372

19864

             }

373

19968

         }

374

375

376

         /**

377

          * @return the element name which caused the problem

378

          */

379

         public String getElementName() {

380

0

             return elementName;

381

         }

382

383

         /**

384

          * Sets the element name which caused the problem

385

          */

386

         public void setElementName(String elementName) {

387

19968

             this.elementName = elementName;

388

19968

         }

389

         /**

390

          * @return the line number of the tag

391

          */

392

         public int getLineNumber() {

393

0

             return lineNumber;

394

         }

395

396

         /**

397

          * Sets the line number of the tag

398

          */

399

         public void setLineNumber(int lineNumber) {

400

19968

             this.lineNumber = lineNumber;

401

19968

         }

402

403

         /**

404

          * @return the column number of the tag

405

          */

406

         public int getColumnNumber() {

407

0

             return columnNumber;

408

         }

409

410

         /**

411

          * Sets the column number of the tag

412

          */

413

         public void setColumnNumber(int columnNumber) {

414

19968

             this.columnNumber = columnNumber;

415

19968

         }

416

417

         /**

418

          * Returns the SAX attributes of this tag

419

          * @return Attributes

420

          */

421

         public Attributes getSaxAttributes() {

422

9646

             return saxAttributes;

423

         }

424

425

         /**

426

          * Sets the SAX attributes of this tag

427

          * @param saxAttributes The saxAttributes to set

428

          */

429

         public void setSaxAttributes(Attributes saxAttributes) {

430

19721

             this.saxAttributes = saxAttributes;

431

19721

         }

432

433

         /**

434

          * Returns the local, non namespaced XML name of this tag

435

          * @return String

436

          */

437

         public String getLocalName() {

438

0

             return localName;

439

         }

440

441

         /**

442

          * Sets the local, non namespaced name of this tag.

443

          * @param localName The localName to set

444

          */

445

         public void setLocalName(String localName) {

446

19968

             this.localName = localName;

447

19968

         }

448

449

450

         /**

451

          * Returns the namespace context of this tag. This is all the prefixes

452

          * in scope in the document where this tag is used which are mapped to

453

          * their namespace URIs.

454

          *

455

          * @return a Map with the keys are namespace prefixes and the values are

456

          * namespace URIs.

457

          */

458

         public synchronized Map getNamespaceContext() {

459

0

             if (namespaceContext == null) {

460

0

                 if (parent != null) {

461

0

                     namespaceContext = getParent().getNamespaceContext();

462

0

                     if (tagNamespacesMap != null && !tagNamespacesMap.isEmpty()) {

463

                         // create a new child context

464

0

                         Hashtable newContext = new Hashtable(namespaceContext.size()+1);

465

0

                         newContext.putAll(namespaceContext);

466

0

                         newContext.putAll(tagNamespacesMap);

467

0

                         namespaceContext = newContext;

468

0

                     }

469

                 }

470

                 else {

471

0

                     namespaceContext = tagNamespacesMap;

472

0

                     if (namespaceContext == null) {

473

0

                         namespaceContext = new Hashtable();

474

                     }

475

                 }

476

             }

477

0

             return namespaceContext;

478

         }

479

480

         // Implementation methods

481

         //-------------------------------------------------------------------------

482

483

         /**

484

          * Factory method to create a new Tag instance.

485

          * The default implementation is to delegate to the TagFactory

486

          */

487

         protected Tag createTag() throws JellyException {

488

9646

             if ( tagFactory != null) {

489

9646

                 return tagFactory.createTag(localName, getSaxAttributes());

490

             }

491

0

             return null;

492

         }

493

494

495

         /**

496

          * Compiles a newly created tag if required, sets its parent and body.

497

          */

498

         protected void configureTag(Tag tag, JellyContext context) throws JellyException {

499

9646

             if (tag instanceof CompilableTag) {

500

0

                 ((CompilableTag) tag).compile();

501

             }

502

9646

             Tag parentTag = null;

503

9646

             if ( parent != null ) {

504

8671

                 parentTag = parent.getTag(context);

505

             }

506

9646

             tag.setParent( parentTag );

507

9646

             tag.setBody( tagBody );

508

509

9646

             if (tag instanceof NamespaceAwareTag) {

510

0

                 NamespaceAwareTag naTag = (NamespaceAwareTag) tag;

511

0

                 naTag.setNamespaceContext(getNamespaceContext());

512

             }

513

9646

             if (tag instanceof LocationAware) {

514

0

                 applyLocation((LocationAware) tag);

515

             }

516

9646

         }

517

518

519

         /**

520

          * Allows the script to set the tag instance to be used, such as in a StaticTagScript

521

          * when a StaticTag is switched with a DynamicTag

522

          */

523

         protected void setTag(Tag tag, JellyContext context) {

524

156

             Thread t = Thread.currentThread();

525

156

             threadLocalTagCache.put(t,tag);

526

156

         }

527

528

         /**

529

          * Output the new namespace prefixes used for this element

530

          */

531

         protected void startNamespacePrefixes(XMLOutput output) throws SAXException {

532

156

             if ( tagNamespacesMap != null ) {

533

13

                 for ( Iterator iter = tagNamespacesMap.entrySet().iterator(); iter.hasNext(); ) {

534

13

                     Map.Entry entry = (Map.Entry) iter.next();

535

13

                     String prefix = (String) entry.getKey();

536

13

                     String uri = (String) entry.getValue();

537

13

                     output.startPrefixMapping(prefix, uri);

538

13

                 }

539

             }

540

156

         }

541

542

         /**

543

          * End the new namespace prefixes mapped for the current element

544

          */

545

         protected void endNamespacePrefixes(XMLOutput output) throws SAXException {

546

156

             if ( tagNamespacesMap != null ) {

547

13

                 for ( Iterator iter = tagNamespacesMap.keySet().iterator(); iter.hasNext(); ) {

548

13

                     String prefix = (String) iter.next();

549

13

                     output.endPrefixMapping(prefix);

550

13

                 }

551

             }

552

156

         }

553

554

         /**

555

          * Converts the given value to the required type.

556

          *

557

          * @param value is the value to be converted. This will not be null

558

          * @param requiredType the type that the value should be converted to

559

          */

560

         protected Object convertType(Object value, Class requiredType)

561

             throws JellyException {

562

0

             if (requiredType.isInstance(value)) {

563

0

                 return value;

564

             }

565

0

             if (value instanceof String) {

566

0

                 return ConvertUtils.convert((String) value, requiredType);

567

             }

568

0

             return value;

569

         }

570

571

         /**

572

          * Creates a new Jelly exception, adorning it with location information

573

          */

574

         protected JellyException createJellyException(String reason) {

575

0

             return new JellyException(

576

                 reason, fileName, elementName, columnNumber, lineNumber

577

             );

578

         }

579

580

         /**

581

          * Creates a new Jelly exception, adorning it with location information

582

          */

583

         protected JellyException createJellyException(String reason, Exception cause) {

584

0

             if (cause instanceof JellyException) {

585

0

                 return (JellyException) cause;

586

             }

587

588

0

             if (cause instanceof InvocationTargetException) {

589

0

                 return new JellyException(

590

                     reason,

591

                     ((InvocationTargetException) cause).getTargetException(),

592

                     fileName,

593

                     elementName,

594

                     columnNumber,

595

                     lineNumber);

596

             }

597

0

             return new JellyException(

598

                 reason, cause, fileName, elementName, columnNumber, lineNumber

599

             );

600

         }

601

602

         /**

603

          * A helper method to handle this Jelly exception.

604

          * This method adorns the JellyException with location information

605

          * such as adding line number information etc.

606

          */

607

         protected void handleException(JellyTagException e) throws JellyTagException {

608

637

             if (log.isTraceEnabled()) {

609

0

                 log.trace( "Caught exception: " + e, e );

610

             }

611

612

637

             applyLocation(e);

613

614

637

             throw e;

615

         }

616

617

         /**

618

          * A helper method to handle this Jelly exception.

619

          * This method adorns the JellyException with location information

620

          * such as adding line number information etc.

621

          */

622

         protected void handleException(JellyException e) throws JellyTagException {

623

0

             if (log.isTraceEnabled()) {

624

0

                 log.trace( "Caught exception: " + e, e );

625

             }

626

627

0

             applyLocation(e);

628

629

0

             throw new JellyTagException(e);

630

         }

631

632

         protected void applyLocation(LocationAware locationAware) {

633

637

             if (locationAware.getLineNumber() == -1) {

634

338

                 locationAware.setColumnNumber(columnNumber);

635

338

                 locationAware.setLineNumber(lineNumber);

636

             }

637

637

             if ( locationAware.getFileName() == null ) {

638

338

                 locationAware.setFileName( fileName );

639

             }

640

637

             if ( locationAware.getElementName() == null ) {

641

338

                 locationAware.setElementName( elementName );

642

             }

643

637

         }

644

645

         /**

646

          * A helper method to handle this non-Jelly exception.

647

          * This method will rethrow the exception, wrapped in a JellyException

648

          * while adding line number information etc.

649

          */

650

         protected void handleException(Exception e) throws JellyTagException {

651

0

             if (log.isTraceEnabled()) {

652

0

                 log.trace( "Caught exception: " + e, e );

653

             }

654

655

0

             if (e instanceof LocationAware) {

656

0

                 applyLocation((LocationAware) e);

657

             }

658

659

0

             if ( e instanceof JellyException ) {

660

0

                 e.fillInStackTrace();

661

             }

662

663

0

             if ( e instanceof InvocationTargetException) {

664

0

                 throw new JellyTagException( ((InvocationTargetException)e).getTargetException(),

665

                                           fileName,

666

                                           elementName,

667

                                           columnNumber,

668

                                           lineNumber );

669

             }

670

671

0

             throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);

672

         }

673

674

         /**

675

          * A helper method to handle this non-Jelly exception.

676

          * This method will rethrow the exception, wrapped in a JellyException

677

          * while adding line number information etc.

678

          *

679

          * Is this method wise?

680

          */

681

         protected void handleException(Error e) throws Error, JellyTagException {

682

0

             if (log.isTraceEnabled()) {

683

0

                 log.trace( "Caught exception: " + e, e );

684

             }

685

686

0

             if (e instanceof LocationAware) {

687

0

                 applyLocation((LocationAware) e);

688

             }

689

690

0

             throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);

691

         }

692

     }

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.